@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.
@@ -406,6 +406,49 @@ function parseColorToRgb(s) {
406
406
  }
407
407
  return null;
408
408
  }
409
+ var OKLCH_RE = /^oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*[\d.]+)?\s*\)$/i;
410
+ function parseOklchToOklab(s) {
411
+ const m = OKLCH_RE.exec(s.trim());
412
+ if (!m) {
413
+ return null;
414
+ }
415
+ const L = parseFloat(m[1]);
416
+ const C = parseFloat(m[2]);
417
+ const H = parseFloat(m[3]);
418
+ if (Number.isNaN(L) || Number.isNaN(C) || Number.isNaN(H)) {
419
+ return null;
420
+ }
421
+ const clampedL = Math.max(0, Math.min(1, L));
422
+ const clampedC = Math.max(0, Math.min(0.4, C));
423
+ const H_rad = H * (Math.PI / 180);
424
+ return {
425
+ L: clampedL,
426
+ a: clampedC * Math.cos(H_rad),
427
+ b: clampedC * Math.sin(H_rad)
428
+ };
429
+ }
430
+ function parseColorToOklab(s) {
431
+ const oklab = parseOklchToOklab(s);
432
+ if (oklab !== null) {
433
+ return oklab;
434
+ }
435
+ const rgb = parseColorToRgb(s);
436
+ if (rgb === null) {
437
+ return null;
438
+ }
439
+ return rgbToOklab(rgb);
440
+ }
441
+ function colorToRgb(color) {
442
+ const rgb = parseColorToRgb(color);
443
+ if (rgb !== null) {
444
+ return rgb;
445
+ }
446
+ const lab = parseOklchToOklab(color);
447
+ if (lab !== null) {
448
+ return oklabToRgb(lab);
449
+ }
450
+ throw new Error(`[sarmal] unrecognized color "${color}"`);
451
+ }
409
452
  function srgbByteToLinear(c) {
410
453
  const n = c / 255;
411
454
  return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
@@ -443,26 +486,25 @@ var lerpOklab = (a, b, t) => {
443
486
  if (t >= 1) {
444
487
  return b;
445
488
  }
446
- const la = rgbToOklab(a), lb = rgbToOklab(b);
447
- return oklabToRgb({
448
- L: la.L + (lb.L - la.L) * t,
449
- a: la.a + (lb.a - la.a) * t,
450
- b: la.b + (lb.b - la.b) * t
451
- });
489
+ return {
490
+ L: a.L + (b.L - a.L) * t,
491
+ a: a.a + (b.a - a.a) * t,
492
+ b: a.b + (b.b - a.b) * t
493
+ };
452
494
  };
453
495
  function getPaletteColor(palette, position, timeOffset = 0) {
454
496
  if (palette.length === 0) {
455
- return { r: 255, g: 255, b: 255 };
497
+ return { L: 1, a: 0, b: 0 };
456
498
  }
457
499
  if (palette.length === 1) {
458
- return hexToRgb(palette[0]);
500
+ return palette[0];
459
501
  }
460
502
  const cyclePos = ((position + timeOffset) % 1 + 1) % 1;
461
503
  const scaled = cyclePos * palette.length;
462
504
  const idx = Math.floor(scaled);
463
505
  const t = scaled - idx;
464
- const c1 = hexToRgb(palette[idx % palette.length]);
465
- const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
506
+ const c1 = palette[idx % palette.length];
507
+ const c2 = palette[(idx + 1) % palette.length];
466
508
  return lerpOklab(c1, c2, t);
467
509
  }
468
510
  var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
@@ -497,9 +539,9 @@ function validateRenderOptions(partial) {
497
539
  }
498
540
  function assertTrailColor(value) {
499
541
  if (typeof value === "string") {
500
- if (parseColorToRgb(value) === null) {
542
+ if (parseColorToOklab(value) === null) {
501
543
  throw new TypeError(
502
- `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got "${value}"`
544
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()), got "${value}"`
503
545
  );
504
546
  }
505
547
  return;
@@ -512,25 +554,25 @@ function assertTrailColor(value) {
512
554
  }
513
555
  for (let i = 0; i < value.length; i++) {
514
556
  const entry = value[i];
515
- if (typeof entry !== "string" || parseColorToRgb(entry) === null) {
557
+ if (typeof entry !== "string" || parseColorToOklab(entry) === null) {
516
558
  throw new TypeError(
517
- `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got ${JSON.stringify(entry)}`
559
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()), got ${JSON.stringify(entry)}`
518
560
  );
519
561
  }
520
562
  }
521
563
  return;
522
564
  }
523
565
  throw new TypeError(
524
- `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or an array of color strings, got ${JSON.stringify(value)}`
566
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or an array of color strings, got ${JSON.stringify(value)}`
525
567
  );
526
568
  }
527
569
  function assertHeadColor(value) {
528
570
  if (value === null) {
529
571
  return;
530
572
  }
531
- if (typeof value !== "string" || parseColorToRgb(value) === null) {
573
+ if (typeof value !== "string" || parseColorToOklab(value) === null) {
532
574
  throw new TypeError(
533
- `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or null, got ${JSON.stringify(value)}`
575
+ `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or null, got ${JSON.stringify(value)}`
534
576
  );
535
577
  }
536
578
  }
@@ -538,9 +580,9 @@ function assertSkeletonColor(value) {
538
580
  if (value === "transparent") {
539
581
  return;
540
582
  }
541
- if (typeof value !== "string" || parseColorToRgb(value) === null) {
583
+ if (typeof value !== "string" || parseColorToOklab(value) === null) {
542
584
  throw new TypeError(
543
- `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or "transparent", got ${JSON.stringify(value)}`
585
+ `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or "transparent", got ${JSON.stringify(value)}`
544
586
  );
545
587
  }
546
588
  }
@@ -575,7 +617,7 @@ function resolveHeadColor(trailColor, trailStyle) {
575
617
  }
576
618
  const palette = resolveTrailPalette(trailColor);
577
619
  const last = palette[palette.length - 1];
578
- const { r, g, b } = parseColorToRgb(last);
620
+ const { r, g, b } = colorToRgb(last);
579
621
  return `rgb(${r},${g},${b})`;
580
622
  }
581
623
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
@@ -596,8 +638,8 @@ function warnIfTrailColorMismatch(trailColor, trailStyle) {
596
638
  var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
597
639
  var WHITE_HEX = "#ffffff";
598
640
  function colorToRgbComponents(color) {
599
- const c = parseColorToRgb(color);
600
- return `${c.r},${c.g},${c.b}`;
641
+ const { r, g, b } = colorToRgb(color);
642
+ return `${r},${g},${b}`;
601
643
  }
602
644
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
603
645
  target.style.width = `${logicalWidth}px`;
@@ -619,6 +661,7 @@ function createRenderer(options) {
619
661
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
620
662
  let trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
621
663
  let trailPalette = resolveTrailPalette(trailColor);
664
+ let trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
622
665
  warnIfTrailColorMismatch(trailColor, trailStyle);
623
666
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
624
667
  function setupCanvas() {
@@ -730,8 +773,8 @@ function createRenderer(options) {
730
773
  ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
731
774
  } else {
732
775
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
733
- const color = getPaletteColor(trailPalette, progress, timeOffset);
734
- ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${opacity})`;
776
+ const { r, g, b } = oklabToRgb(getPaletteColor(trailPaletteOklab, progress, timeOffset));
777
+ ctx.fillStyle = `rgba(${r},${g},${b},${opacity})`;
735
778
  }
736
779
  ctx.beginPath();
737
780
  ctx.moveTo(l0x, l0y);
@@ -865,6 +908,7 @@ function createRenderer(options) {
865
908
  trailColor = partial.trailColor;
866
909
  trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
867
910
  trailPalette = resolveTrailPalette(trailColor);
911
+ trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
868
912
  }
869
913
  if (partial.skeletonColor !== void 0) {
870
914
  skeletonColor = partial.skeletonColor;
@@ -948,8 +992,8 @@ function el(tag) {
948
992
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
949
993
  }
950
994
  function colorToRgbAttr(color) {
951
- const c = parseColorToRgb(color);
952
- return `rgb(${c.r},${c.g},${c.b})`;
995
+ const { r, g, b } = colorToRgb(color);
996
+ return `rgb(${r},${g},${b})`;
953
997
  }
954
998
  function createSVGRenderer(options) {
955
999
  const { container, engine } = options;
@@ -967,6 +1011,7 @@ function createSVGRenderer(options) {
967
1011
  let headRadius;
968
1012
  let trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
969
1013
  let trailPalette = resolveTrailPalette(trailColor);
1014
+ let trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
970
1015
  const ariaLabel = options.ariaLabel ?? "Loading";
971
1016
  warnIfTrailColorMismatch(trailColor, trailStyle);
972
1017
  const viewSize = 100;
@@ -1081,7 +1126,7 @@ function createSVGRenderer(options) {
1081
1126
  trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
1082
1127
  if (trailStyle !== "default") {
1083
1128
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
1084
- const { r, g, b } = getPaletteColor(trailPalette, progress, timeOffset);
1129
+ const { r, g, b } = oklabToRgb(getPaletteColor(trailPaletteOklab, progress, timeOffset));
1085
1130
  trailPaths[i].setAttribute("fill", `rgb(${r},${g},${b})`);
1086
1131
  }
1087
1132
  }
@@ -1233,6 +1278,7 @@ function createSVGRenderer(options) {
1233
1278
  trailColor = partial.trailColor;
1234
1279
  trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
1235
1280
  trailPalette = resolveTrailPalette(trailColor);
1281
+ trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
1236
1282
  if (trailStyle === "default") {
1237
1283
  for (const p of trailPaths) {
1238
1284
  p.setAttribute("fill", trailSolid);