saturon 0.2.3 → 0.3.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.
@@ -1,7 +1,5 @@
1
- import { Color } from "./Color.js";
2
- import { config } from "./config.js";
3
1
  import { HSL_to_RGB, HWB_to_RGB, LAB_to_LCH, LAB_to_XYZD50, LCH_to_LAB, MATRICES, OKLAB_to_OKLCH, OKLAB_to_XYZD65, OKLCH_to_OKLAB, RGB_to_HSL, RGB_to_HWB, RGB_to_XYZD65, XYZD50_to_LAB, XYZD65_to_OKLAB, XYZD65_to_RGB, } from "./math.js";
4
- import { modelConverterToColorConverter, spaceConverterToModelConverter, fit, extractBalancedExpression, } from "./utils.js";
2
+ import { spaceConverterToModelConverter } from "./utils.js";
5
3
  /**
6
4
  * A collection of `<named-color>`s and their RGB values.
7
5
  *
@@ -322,6 +320,7 @@ export const colorModels = {
322
320
  r: { index: 0, value: [0, 255], precision: 0 },
323
321
  g: { index: 1, value: [0, 255], precision: 0 },
324
322
  b: { index: 2, value: [0, 255], precision: 0 },
323
+ alpha: { index: 3, value: [0, 1], precision: 3 },
325
324
  },
326
325
  bridge: "xyz-d65",
327
326
  toBridge: RGB_to_XYZD65,
@@ -334,6 +333,7 @@ export const colorModels = {
334
333
  h: { index: 0, value: "hue", precision: 0 },
335
334
  s: { index: 1, value: "percentage", precision: 0 },
336
335
  l: { index: 2, value: "percentage", precision: 0 },
336
+ alpha: { index: 3, value: [0, 1], precision: 3 },
337
337
  },
338
338
  bridge: "rgb",
339
339
  toBridge: HSL_to_RGB,
@@ -344,6 +344,7 @@ export const colorModels = {
344
344
  h: { index: 0, value: "hue", precision: 0 },
345
345
  w: { index: 1, value: "percentage", precision: 0 },
346
346
  b: { index: 2, value: "percentage", precision: 0 },
347
+ alpha: { index: 3, value: [0, 1], precision: 3 },
347
348
  },
348
349
  bridge: "rgb",
349
350
  toBridge: HWB_to_RGB,
@@ -355,6 +356,7 @@ export const colorModels = {
355
356
  l: { index: 0, value: "percentage", precision: 5 },
356
357
  a: { index: 1, value: [-125, 125], precision: 5 },
357
358
  b: { index: 2, value: [-125, 125], precision: 5 },
359
+ alpha: { index: 3, value: [0, 1], precision: 3 },
358
360
  },
359
361
  bridge: "xyz-d50",
360
362
  toBridge: LAB_to_XYZD50,
@@ -366,6 +368,7 @@ export const colorModels = {
366
368
  l: { index: 0, value: "percentage", precision: 5 },
367
369
  c: { index: 1, value: [0, 150], precision: 5 },
368
370
  h: { index: 2, value: "hue", precision: 5 },
371
+ alpha: { index: 3, value: [0, 1], precision: 3 },
369
372
  },
370
373
  bridge: "lab",
371
374
  toBridge: LCH_to_LAB,
@@ -377,6 +380,7 @@ export const colorModels = {
377
380
  l: { index: 0, value: [0, 1], precision: 5 },
378
381
  a: { index: 1, value: [-0.4, 0.4], precision: 5 },
379
382
  b: { index: 2, value: [-0.4, 0.4], precision: 5 },
383
+ alpha: { index: 3, value: [0, 1], precision: 3 },
380
384
  },
381
385
  bridge: "xyz-d65",
382
386
  toBridge: OKLAB_to_XYZD65,
@@ -388,6 +392,7 @@ export const colorModels = {
388
392
  l: { index: 0, value: [0, 1], precision: 5 },
389
393
  c: { index: 1, value: [0, 0.4], precision: 5 },
390
394
  h: { index: 2, value: "hue", precision: 5 },
395
+ alpha: { index: 3, value: [0, 1], precision: 3 },
391
396
  },
392
397
  bridge: "oklab",
393
398
  toBridge: OKLCH_to_OKLAB,
@@ -395,422 +400,3 @@ export const colorModels = {
395
400
  },
396
401
  ...colorSpaces,
397
402
  };
398
- /**
399
- * A collection of `<color-function>`s as `<color>` converters.
400
- *
401
- * @see {@link https://www.w3.org/TR/css-color-5/|CSS Color Module Level 5}
402
- */
403
- export const colorFunctions = Object.fromEntries(Object.entries(colorModels).map(([name, converter]) => [
404
- name,
405
- modelConverterToColorConverter(name, converter),
406
- ]));
407
- /**
408
- * A collection of `<color-base>`s as `<color>` converters.
409
- *
410
- * @see {@link https://www.w3.org/TR/css-color-5/|CSS Color Module Level 5}
411
- */
412
- export const colorBases = {
413
- "hex-color": {
414
- isValid: (str) => str[0] === "#",
415
- bridge: "rgb",
416
- toBridge: (coords) => coords,
417
- parse: (str) => {
418
- const HEX = str.slice(1);
419
- if (![3, 4, 6, 8].includes(HEX.length))
420
- throw new Error("Invalid hex color length.");
421
- for (const ch of HEX)
422
- if (!/^[0-9a-fA-F]$/.test(ch))
423
- throw new Error("Invalid hex color character.");
424
- const expand = (c) => parseInt(c.length === 1 ? c + c : c, 16);
425
- const [r, g, b, a = 255] = HEX.length <= 4
426
- ? HEX.split("").map(expand)
427
- : [HEX.slice(0, 2), HEX.slice(2, 4), HEX.slice(4, 6), HEX.slice(6, 8)].map((c) => parseInt(c || "ff", 16));
428
- return [r, g, b, a / 255];
429
- },
430
- fromBridge: (coords) => coords,
431
- format: ([r, g, b, a = 1]) => {
432
- const toHex = (v) => v.toString(16).padStart(2, "0");
433
- const hex = [r, g, b].map((v) => toHex(Math.round(Math.max(0, Math.min(255, v))))).join("");
434
- return `#${hex}${a < 1 ? toHex(Math.round(a * 255)) : ""}`;
435
- },
436
- },
437
- ...colorFunctions,
438
- "named-color": {
439
- isValid: (str) => Object.keys(namedColors).some((key) => key === str),
440
- bridge: "rgb",
441
- toBridge: (coords) => coords,
442
- parse: (name) => {
443
- const rgb = namedColors[name];
444
- if (!rgb)
445
- throw new Error(`Invalid named-color: ${name}.`);
446
- return [...rgb, 1];
447
- },
448
- fromBridge: (coords) => coords,
449
- format: (rgb) => {
450
- const [r, g, b] = rgb.map((v, i) => (i < 3 ? Math.round(Math.min(255, Math.max(0, v))) : v));
451
- for (const [name, [nr, ng, nb]] of Object.entries(namedColors)) {
452
- if (r === nr && g === ng && b === nb)
453
- return name;
454
- }
455
- },
456
- },
457
- "color-mix": {
458
- isValid: (str) => str.slice(0, 10) === "color-mix(" && str[str.length - 1] === ")",
459
- bridge: "rgb",
460
- toBridge: (coords) => coords,
461
- parse: (str) => {
462
- const extractColorAndWeight = (colorStr) => {
463
- const s = colorStr.trim();
464
- let weight;
465
- let remaining = s;
466
- const leadingWeightMatch = remaining.match(/^(\d+)%\s+/);
467
- if (leadingWeightMatch) {
468
- const raw = parseInt(leadingWeightMatch[1], 10);
469
- weight = Math.min(1, Math.max(0, raw / 100));
470
- remaining = remaining.slice(leadingWeightMatch[0].length).trim();
471
- }
472
- let colorExpression = "";
473
- let rest = "";
474
- if (/^[a-z]/i.test(remaining)) {
475
- const { expression: expr, end: e } = extractBalancedExpression(remaining, 0);
476
- if (expr) {
477
- colorExpression = expr;
478
- rest = remaining.slice(e).trim();
479
- }
480
- else {
481
- const m = remaining.match(/^([^\s]+)(.*)$/);
482
- colorExpression = m ? m[1] : remaining;
483
- rest = m ? m[2].trim() : "";
484
- }
485
- }
486
- else {
487
- const m = remaining.match(/^([^\s]+)(.*)$/);
488
- colorExpression = m ? m[1] : remaining;
489
- rest = m ? m[2].trim() : "";
490
- }
491
- if (weight === undefined) {
492
- const trailingWeightMatch = rest.match(/^(-?(?:\d+\.?\d*|\.\d+))%/);
493
- if (trailingWeightMatch) {
494
- const raw = parseInt(trailingWeightMatch[1], 10);
495
- weight = raw / 100;
496
- rest = rest.slice(trailingWeightMatch[0].length).trim();
497
- if (weight < 0) {
498
- throw new Error("Percentages less than 0 are not valid.");
499
- }
500
- else if (weight > 1) {
501
- throw new Error("Percentages greater than 100 are not valid.");
502
- }
503
- }
504
- else if (rest.slice(0, 5) === "calc(") {
505
- const { expression: calcExpr, end } = extractBalancedExpression(rest, 0);
506
- if (!calcExpr) {
507
- throw new Error("Malformed calc() weight expression.");
508
- }
509
- rest = rest.slice(end).trim();
510
- }
511
- }
512
- if (rest.length > 0) {
513
- throw new Error(`Unexpected extra tokens after color: '${rest}'.`);
514
- }
515
- const color = Color.from(colorExpression);
516
- return { color, weight };
517
- };
518
- const getWeight2Prime = (weight1, weight2) => {
519
- if (weight1 === undefined && typeof weight2 === "number") {
520
- weight1 = 1 - weight2;
521
- }
522
- else if (typeof weight1 === "number" && weight2 === undefined) {
523
- weight2 = 1 - weight1;
524
- }
525
- else if (weight1 === undefined && weight2 === undefined) {
526
- weight1 = weight2 = 0.5;
527
- }
528
- else {
529
- weight1 = weight1;
530
- weight2 = weight2;
531
- if (weight1 + weight2 <= 0) {
532
- throw new Error("Sum of percengates cannot be 0%.");
533
- }
534
- }
535
- const totalWeight = weight1 + weight2;
536
- let alphaMultiplier;
537
- if (totalWeight > 1) {
538
- weight1 /= totalWeight;
539
- weight2 /= totalWeight;
540
- }
541
- else {
542
- weight1 /= totalWeight;
543
- weight2 /= totalWeight;
544
- alphaMultiplier = totalWeight;
545
- }
546
- return { amount: weight2, alphaMultiplier };
547
- };
548
- const fnName = "color-mix";
549
- const { expression } = extractBalancedExpression(str, fnName.length);
550
- if (!expression)
551
- throw new Error("Malformed color-mix expression.");
552
- const inner = expression.slice(1, -1).trim();
553
- const parts = [];
554
- let i = 0;
555
- let current = "";
556
- while (i < inner.length) {
557
- const char = inner[i];
558
- if (char === ",") {
559
- parts.push(current.trim());
560
- current = "";
561
- i++;
562
- continue;
563
- }
564
- if (char === "(" || /[a-zA-Z]/.test(char)) {
565
- const { expression: expr, end } = extractBalancedExpression(inner, i);
566
- if (expr) {
567
- current += expr;
568
- i = end;
569
- continue;
570
- }
571
- }
572
- current += char;
573
- i++;
574
- }
575
- parts.push(current.trim());
576
- if (parts.length !== 3) {
577
- throw new Error("color-mix must have three comma-separated parts.");
578
- }
579
- const inPart = parts[0];
580
- const inMatch = inPart.match(/^in\s+([a-z0-9-]+)(?:\s+(shorter|longer|increasing|decreasing)\s+hue)?$/);
581
- if (!inMatch) {
582
- throw new Error("Invalid model and hue format.");
583
- }
584
- const model = inMatch[1];
585
- let hue = inMatch[2];
586
- if (model === "rgb")
587
- throw new Error(`RGB model is not allowed in color-mix(). Use "srgb" instead.`);
588
- if (hue) {
589
- const comps = colorModels[model].components;
590
- if (!comps) {
591
- throw new Error(`Unknown color model: ${model}.`);
592
- }
593
- const hasHue = Object.values(comps).some((c) => c.value === "hue");
594
- if (!hasHue) {
595
- throw new Error(`Hue interpolation not supported in ${model} space.`);
596
- }
597
- }
598
- else {
599
- hue = "shorter";
600
- }
601
- const { color: color1, weight: weight1 } = extractColorAndWeight(parts[1]);
602
- const { color: color2, weight: weight2 } = extractColorAndWeight(parts[2]);
603
- const { amount, alphaMultiplier = 1 } = getWeight2Prime(weight1, weight2);
604
- return color1
605
- .with({ alpha: (a) => (isNaN(a) ? a : a * alphaMultiplier) }, false)
606
- .in(model)
607
- .mix(color2.with({ alpha: (a) => (isNaN(a) ? a : a * alphaMultiplier) }, false), { amount, hue })
608
- .in("rgb").coords;
609
- },
610
- },
611
- transparent: {
612
- isValid: (str) => str === "transparent",
613
- bridge: "rgb",
614
- toBridge: (coords) => coords,
615
- parse: (str) => [NaN, NaN, NaN, 0], // eslint-disable-line no-unused-vars
616
- },
617
- };
618
- /**
619
- * A collection of `<color>` converters.
620
- *
621
- * @see {@link https://www.w3.org/TR/css-color-5/|CSS Color Module Level 5}
622
- */
623
- export const colorTypes = {
624
- ...colorBases,
625
- currentColor: {
626
- isValid: (str) => str === "currentcolor",
627
- bridge: "rgb",
628
- toBridge: (coords) => coords,
629
- parse: (str) => [0, 0, 0, 1], // eslint-disable-line no-unused-vars
630
- },
631
- "system-color": {
632
- isValid: (str) => Object.keys(config.systemColors).some((key) => key.toLowerCase() === str),
633
- bridge: "rgb",
634
- toBridge: (coords) => coords,
635
- parse: (str) => {
636
- const { systemColors } = config;
637
- const key = Object.keys(systemColors).find((k) => k.toLowerCase() === str);
638
- const rgbArr = systemColors[key][config.theme === "light" ? 0 : 1];
639
- return [...rgbArr, 1];
640
- },
641
- },
642
- "contrast-color": {
643
- isValid: (str) => str.slice(0, 15) === "contrast-color(" && str[str.length - 1] === ")",
644
- bridge: "rgb",
645
- toBridge: (coords) => coords,
646
- parse: (str) => {
647
- const inner = str.slice(15, -1);
648
- const [, luminance] = Color.from(inner).in("xyz-d65").coords;
649
- return luminance > 0.5 ? [0, 0, 0, 1] : [255, 255, 255, 1];
650
- },
651
- },
652
- "device-cmyk": {
653
- isValid: (str) => str.slice(0, 12) === "device-cmyk(" && str[str.length - 1] === ")",
654
- bridge: "rgb",
655
- toBridge: (coords) => coords,
656
- parse: (str) => {
657
- const fnName = "device-cmyk";
658
- const fnIndex = str.indexOf(fnName);
659
- if (fnIndex === -1)
660
- throw new Error("Invalid device-cmyk syntax");
661
- const { expression } = extractBalancedExpression(str, fnIndex + fnName.length);
662
- if (!expression)
663
- throw new Error("Malformed device-cmyk expression");
664
- const content = expression.slice(1, -1).trim();
665
- const tokens = [];
666
- let i = 0;
667
- while (i < content.length) {
668
- const char = content[i];
669
- if (char === " ") {
670
- i++;
671
- continue;
672
- }
673
- if (char === "(" || /[a-zA-Z-]/.test(char)) {
674
- const { expression: expr, end } = extractBalancedExpression(content, i);
675
- if (expr) {
676
- tokens.push(expr);
677
- i = end;
678
- continue;
679
- }
680
- }
681
- if (/[\d.+-]/.test(char)) {
682
- let num = "";
683
- while (i < content.length && /[\d.eE+-]/.test(content[i])) {
684
- num += content[i];
685
- i++;
686
- }
687
- if (i < content.length && content[i] === "%") {
688
- num += "%";
689
- i++;
690
- }
691
- else if (i < content.length && /[a-zA-Z]/.test(content[i])) {
692
- while (i < content.length && /[a-zA-Z]/.test(content[i])) {
693
- num += content[i];
694
- i++;
695
- }
696
- }
697
- tokens.push(num);
698
- continue;
699
- }
700
- if (char === "," || char === "/") {
701
- tokens.push(char);
702
- i++;
703
- continue;
704
- }
705
- throw new Error(`Unexpected character: ${char}`);
706
- }
707
- const parseValue = (token) => {
708
- return token[token.length - 1] === "%" ? parseFloat(token) / 100 : parseFloat(token);
709
- };
710
- if (tokens.length >= 2 && tokens[1] === ",") {
711
- const values = tokens.filter((t) => t !== ",");
712
- if (values.length === 4 || values.length === 5) {
713
- const [c, m, y, k, alpha = 1] = values.map(parseValue);
714
- const red = 1 - Math.min(1, c * (1 - k) + k);
715
- const green = 1 - Math.min(1, m * (1 - k) + k);
716
- const blue = 1 - Math.min(1, y * (1 - k) + k);
717
- return [red * 255, green * 255, blue * 255, alpha];
718
- }
719
- throw new Error("Invalid number of components for comma-separated device-cmyk");
720
- }
721
- let idx = 0;
722
- const components = [];
723
- while (idx < tokens.length && components.length < 4 && tokens[idx] !== "/" && tokens[idx] !== ",") {
724
- components.push(tokens[idx]);
725
- idx++;
726
- }
727
- if (components.length !== 4) {
728
- throw new Error("Invalid number of components for space-separated device-cmyk");
729
- }
730
- const [c, m, y, k] = components.map(parseValue);
731
- let alpha = 1;
732
- if (idx < tokens.length && tokens[idx] === "/") {
733
- idx++;
734
- if (idx >= tokens.length)
735
- throw new Error("Missing alpha value");
736
- alpha = parseValue(tokens[idx]);
737
- idx++;
738
- }
739
- if (idx < tokens.length && tokens[idx] === ",") {
740
- idx++;
741
- const fallbackStr = tokens.slice(idx).join(" ");
742
- return Color.from(fallbackStr).in("rgb").coords;
743
- }
744
- const red = 1 - Math.min(1, c * (1 - k) + k);
745
- const green = 1 - Math.min(1, m * (1 - k) + k);
746
- const blue = 1 - Math.min(1, y * (1 - k) + k);
747
- return [red * 255, green * 255, blue * 255, alpha];
748
- },
749
- fromBridge: (coords) => coords,
750
- format: ([red, green, blue, alpha = 1], options = {}) => {
751
- const { legacy = false, precision = 3, fit: fitMethod = config.defaults.fit } = options;
752
- const [fr, fg, fb] = fit([red, green, blue], "rgb", { method: fitMethod });
753
- const r = fr / 255;
754
- const g = fg / 255;
755
- const b = fb / 255;
756
- const k = 1 - Math.max(r, g, b);
757
- const formatComponent = (value) => {
758
- return Number(precision === null ? value : value.toFixed(precision)).toString();
759
- };
760
- const c = formatComponent(k === 1 ? 0 : (1 - r - k) / (1 - k));
761
- const m = formatComponent(k === 1 ? 0 : (1 - g - k) / (1 - k));
762
- const y = formatComponent(k === 1 ? 0 : (1 - b - k) / (1 - k));
763
- const kFormatted = formatComponent(k);
764
- const alphaFormatted = Number(alpha.toFixed(3)).toString();
765
- if (legacy) {
766
- return `device-cmyk(${c}, ${m}, ${y}, ${kFormatted}${alpha < 1 ? `, ${alphaFormatted}` : ""})`;
767
- }
768
- const rgbPart = colorFunctions.rgb.format?.([fr, fg, fb, alpha], options);
769
- return `device-cmyk(${c} ${m} ${y} ${kFormatted}${alpha < 1 ? ` / ${alphaFormatted}` : ""}, ${rgbPart})`;
770
- },
771
- },
772
- "light-dark": {
773
- isValid: (str) => str.slice(0, 11) === "light-dark(" && str[str.length - 1] === ")",
774
- bridge: "rgb",
775
- toBridge: (coords) => coords,
776
- parse: (str) => {
777
- const fnName = "light-dark";
778
- const fnIndex = str.indexOf(fnName);
779
- if (fnIndex === -1)
780
- throw new Error("Not a <light-dark()> expression");
781
- const { expression } = extractBalancedExpression(str, fnIndex + fnName.length);
782
- if (!expression)
783
- throw new Error("Malformed <light-dark()> expression");
784
- const inner = expression.slice(1, -1).trim();
785
- const parts = [];
786
- let current = "";
787
- let i = 0;
788
- while (i < inner.length) {
789
- const char = inner[i];
790
- if (char === ",") {
791
- parts.push(current.trim());
792
- current = "";
793
- i++;
794
- continue;
795
- }
796
- if (char === "(" || /[a-zA-Z]/.test(char)) {
797
- const { expression: expr, end } = extractBalancedExpression(inner, i);
798
- if (expr) {
799
- current += expr;
800
- i = end;
801
- continue;
802
- }
803
- }
804
- current += char;
805
- i++;
806
- }
807
- parts.push(current.trim());
808
- if (parts.length !== 2) {
809
- throw new Error("Invalid light-dark format");
810
- }
811
- const [color1, color2] = parts;
812
- const { theme } = config;
813
- return Color.from(theme === "light" ? color1 : color2).coords;
814
- },
815
- },
816
- };
@@ -0,0 +1,148 @@
1
+ import { ColorFormatter, ColorModel, ColorModelConverter, ColorSpace, ColorSpaceConverter, ComponentDefinition, FitFunction, FitMethod, FormattingOptions, GrammarRuleSpec, NamedColor, OutputType, SystemColor } from "./types.js";
2
+ export declare const grammar = "\n/* ==========================================================================\n Core Color Types\n ========================================================================== */\n<color> = <color-base> | currentColor | <system-color> | <contrast-color()> | <device-cmyk()> | <light-dark-color>\n\n<color-base> = <hex-color> | <color-function> | <named-color> | <color-mix()> | transparent\n\n<color-function> = <rgb()> | <rgba()> | <hsl()> | <hsla()> | <hwb()> | <lab()> | <lch()> | <oklab()> | <oklch()> | <alpha()> | <color()> | <custom-color-function>\n\n/* ==========================================================================\n RGB & RGBA Syntax\n ========================================================================== */\n<rgb()> = [ <legacy-rgb-syntax> | <modern-rgb-syntax> | <relative-rgb-syntax> ]\n<rgba()> = [ <legacy-rgba-syntax> | <modern-rgba-syntax> | <relative-rgba-syntax> ]\n\n<legacy-rgb-syntax> = rgb( <percentage>#{3} [ , <alpha-value> ]? ) | rgb( <number>#{3} [ , <alpha-value> ]? ) \n<legacy-rgba-syntax> = rgba( <percentage>#{3} [ , <alpha-value> ]? ) | rgba( <number>#{3} [ , <alpha-value> ]? ) \n\n<modern-rgb-syntax> = rgb( <rgb-absolute-component>{3} [ / <alpha-absolute-value> ]? )\n<modern-rgba-syntax> = rgba( <rgb-absolute-component>{3} [ / <alpha-absolute-value> ]? )\n<rgb-absolute-component> = <number> | <percentage> | none\n<alpha-absolute-value> = <alpha-value> | none\n\n<relative-rgb-syntax> = rgb( from <color> <rgb-relative-component>{3} [ / <rgb-relative-alpha> ]? )\n<relative-rgba-syntax> = rgba( from <color> <rgb-relative-component>{3} [ / <rgb-relative-alpha> ]? )\n\n/* ==========================================================================\n HSL & HSLA Syntax\n ========================================================================== */\n<hsl()> = [ <legacy-hsl-syntax> | <modern-hsl-syntax> | <relative-hsl-syntax> ]\n<hsla()> = [ <legacy-hsla-syntax> | <modern-hsla-syntax> | <relative-hsla-syntax> ]\n\n<legacy-hsl-syntax> = hsl( <hue> , <percentage> , <percentage> [ , <alpha-value> ]? ) \n<legacy-hsla-syntax> = hsla( <hue> , <percentage> , <percentage> [ , <alpha-value> ]? ) \n\n<modern-hsl-syntax> = hsl( [ <hue> | none ] [ <percentage> | <number> | none ]{2} [ / <alpha-absolute-value> ]? )\n<modern-hsla-syntax> = hsla( [ <hue> | none ] [ <percentage> | <number> | none ]{2} [ / <alpha-absolute-value> ]? )\n\n<relative-hsl-syntax> = hsl( from <color> <hsl-relative-hue> <hsl-relative-component>{2} [ / <hsl-relative-alpha> ]? )\n<relative-hsla-syntax> = hsla( from <color> <hsl-relative-hue> <hsl-relative-component>{2} [ / <hsl-relative-alpha> ]? )\n<hsl-relative-hue> = <hue> | none | h | s | l | alpha\n<hsl-relative-component> = <percentage> | <number> | none | h | s | l | alpha\n<hsl-relative-alpha> = <alpha-value> | none | h | s | l | alpha\n\n/* ==========================================================================\n HWB Syntax\n ========================================================================== */\n<hwb()> = [ <absolute-hwb-syntax> | <relative-hwb-syntax> ]\n<absolute-hwb-syntax> = hwb( [ <hue> | none ] [ <percentage> | <number> | none ]{2} [ / <alpha-absolute-value> ]? )\n<relative-hwb-syntax> = hwb( from <color> <hwb-relative-hue> <hwb-relative-component>{2} [ / <hwb-relative-alpha> ]? )\n<hwb-relative-hue> = <hue> | none | h | w | b | alpha\n<hwb-relative-component> = <percentage> | <number> | none | h | w | b | alpha\n<hwb-relative-alpha> = <alpha-value> | none | h | w | b | alpha\n\n/* ==========================================================================\n LAB & LCH Syntax\n ========================================================================== */\n<lab()> = [ <absolute-lab-syntax> | <relative-lab-syntax> ]\n<absolute-lab-syntax> = lab( [ <percentage> | <number> | none ]{3} [ / <alpha-absolute-value> ]? )\n<relative-lab-syntax> = lab( from <color> <lab-relative-component>{3} [ / <lab-relative-alpha> ]? )\n<lab-relative-component> = <percentage> | <number> | none | l | a | b | alpha\n<lab-relative-alpha> = <alpha-value> | none | l | a | b | alpha\n\n<lch()> = [ <absolute-lch-syntax> | <relative-lch-syntax> ]\n<absolute-lch-syntax> = lch( [ <percentage> | <number> | none ]{2} [ <hue> | none ] [ / <alpha-absolute-value> ]? )\n<relative-lch-syntax> = lch( from <color> <lch-relative-component>{2} <lch-relative-hue> [ / <lch-relative-alpha> ]? )\n<lch-relative-component> = <percentage> | <number> | none | l | c | h | alpha\n<lch-relative-hue> = <hue> | none | l | c | h | alpha\n<lch-relative-alpha> = <alpha-value> | none | l | c | h | alpha\n\n/* ==========================================================================\n OKLAB & OKLCH Syntax\n ========================================================================== */\n<oklab()> = [ <absolute-oklab-syntax> | <relative-oklab-syntax> ]\n<absolute-oklab-syntax> = oklab( [ <percentage> | <number> | none ]{3} [ / <alpha-absolute-value> ]? )\n<relative-oklab-syntax> = oklab( from <color> <oklab-relative-component>{3} [ / <oklab-relative-alpha> ]? )\n<oklab-relative-component> = <percentage> | <number> | none | l | a | b | alpha\n<oklab-relative-alpha> = <alpha-value> | none | l | a | b | alpha\n\n<oklch()> = [ <absolute-oklch-syntax> | <relative-oklch-syntax> ]\n<absolute-oklch-syntax> = oklch( [ <percentage> | <number> | none ]{2} [ <hue> | none ] [ / <alpha-absolute-value> ]? )\n<relative-oklch-syntax> = oklch( from <color> <oklch-relative-component>{2} <oklch-relative-hue> [ / <oklch-relative-alpha> ]? )\n<oklch-relative-component> = <percentage> | <number> | none | l | c | h | alpha\n<oklch-relative-hue> = <hue> | none | l | c | h | alpha\n<oklch-relative-alpha> = <alpha-value> | none | l | c | h | alpha\n\n/* ==========================================================================\n ALPHA Syntax\n ========================================================================== */\n<alpha()> = alpha( from <color> [ / [ <alpha-value> | none | alpha ] ]? )\n\n/* ==========================================================================\n COLOR Space Function Syntax\n ========================================================================== */\n<color()> = [ <absolute-color-syntax> | <relative-color-syntax> ]\n<absolute-color-syntax> = color( [ <predefined-rgb> | <xyz-space> | <custom-color-space> ] [ <number> | <percentage> | none ]{3} [ / [ <alpha-value> | none ] ]? )\n<relative-color-syntax> = [ <relative-rgb-color-syntax> | <relative-xyz-color-syntax> | <relative-custom-color-syntax> ]\n\n<relative-rgb-color-syntax> = color( from <color> <predefined-rgb> <rgb-relative-component>{3} [ / <rgb-relative-alpha> ]? )\n\n<relative-xyz-color-syntax> = color( from <color> <xyz-space> <xyz-relative-component>{3} [ / <xyz-relative-alpha> ]? )\n<xyz-relative-component> = <number> | <percentage> | none | x | y | z | alpha\n<xyz-relative-alpha> = <alpha-value> | none | x | y | z | alpha\n\n<predefined-rgb> = srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020\n<xyz-space> = xyz | xyz-d50 | xyz-d65\n\n<relative-custom-color-syntax> = %unregistered-sentinal-value%\n\n/* ==========================================================================\n Functional Utilities (Light/Dark, Contrast, CMYK, Mix)\n ========================================================================== */\n<light-dark-color> = light-dark( <color> , <color> )\n\n<contrast-color()> = contrast-color( <color> )\n\n<device-cmyk()> = <legacy-device-cmyk-syntax> | <modern-device-cmyk-syntax>\n<legacy-device-cmyk-syntax> = device-cmyk( <number>#{4} )\n<modern-device-cmyk-syntax> = device-cmyk( <cmyk-component>{4} [ / [ <alpha-value> | none ] ]? )\n<cmyk-component> = <number> | <percentage> | none\n\n<color-mix()> = color-mix( [ <color-interpolation-method> , ]? [ <color> && <percentage [0,100]>? ]# )\n<color-interpolation-method> = in [ <rectangular-color-space> | <polar-color-space> <hue-interpolation-method>? ]\n<color-space> = <rectangular-color-space> | <polar-color-space>\n<rectangular-color-space> = srgb | srgb-linear | display-p3 | display-p3-linear | a98-rgb | prophoto-rgb | rec2020 | lab | oklab | <xyz-space> | <custom-color-space> | <custom-rectangular-space>\n<polar-color-space> = hsl | hwb | lch | oklch | <custom-polar-space>\n<hue-interpolation-method> = [ shorter | longer | increasing | decreasing ] hue\n\n<custom-color-function> = %unregistered-sentinel-value%\n<custom-rectangular-space> = %unregistered-sentinel-value%\n<custom-polar-space> = %unregistered-sentinel-value%\n\n/* ==========================================================================\n Shared Value Components\n ========================================================================== */\n<alpha-value> = <number> | <percentage>\n<hue> = <number> | <angle>\n<rgb-relative-component> = <number> | <percentage> | none | r | g | b | alpha\n<rgb-relative-alpha> = <alpha-value> | none | r | g | b | alpha\n<custom-color-space> = %unregistered-sentinel-value%\n";
3
+ export declare const grammarMap: Record<string, GrammarNode>;
4
+ type GrammarNode = SequenceNode | ChoiceNode | LiteralNode | RefNode | OptionalNode | RepeatNode | PermutationNode | HashListNode;
5
+ type PermutationNode = {
6
+ kind: "permutation";
7
+ nodes: GrammarNode[];
8
+ };
9
+ type HashListNode = {
10
+ kind: "hashList";
11
+ node: GrammarNode;
12
+ };
13
+ type SequenceNode = {
14
+ kind: "sequence";
15
+ nodes: GrammarNode[];
16
+ };
17
+ type ChoiceNode = {
18
+ kind: "choice";
19
+ nodes: GrammarNode[];
20
+ };
21
+ type LiteralNode = {
22
+ kind: "literal";
23
+ value: string;
24
+ };
25
+ type RefNode = {
26
+ kind: "ref";
27
+ name: string;
28
+ args?: string[];
29
+ };
30
+ type OptionalNode = {
31
+ kind: "optional";
32
+ node: GrammarNode;
33
+ };
34
+ type RepeatNode = {
35
+ kind: "repeat";
36
+ node: GrammarNode;
37
+ min: number;
38
+ max?: number;
39
+ };
40
+ export type ParseNode = {
41
+ type: string;
42
+ value: string | ParseNode[];
43
+ };
44
+ type MatchResult = {
45
+ success: boolean;
46
+ nextIndex: number;
47
+ nodes: ParseNode[];
48
+ };
49
+ export type ParseTreeNode<T extends string = string> = {
50
+ type: T;
51
+ value: T extends "literal" | "symbol" ? string : string | ParseTreeNode[];
52
+ };
53
+ export declare function validateTokens(tokens: string[], rootRule?: string): ParseNode;
54
+ export declare function matchRule(ruleName: string, tokens: string[], index: number, grammar: Record<string, GrammarNode>, memo: Map<string, MatchResult[]>, currentFunction: string | null, args?: string[]): MatchResult[];
55
+ export declare function matchNode(node: GrammarNode, tokens: string[], index: number, grammar: Record<string, GrammarNode>, memo: Map<string, MatchResult[]>, currentFunction: string | null): MatchResult[];
56
+ export declare function tokenize(input: string): string[];
57
+ export declare function parseGrammar(grammar: string): Record<string, GrammarNode>;
58
+ export declare function parseExpression(expr: string): GrammarNode;
59
+ export declare function evaluateComponent(node: ParseTreeNode, expectedType: ComponentDefinition["value"], base?: Record<string, number>): number;
60
+ export declare function evaluateCSSValue(tokenString: string, expectedType: ComponentDefinition["value"], base?: Record<string, number>): number;
61
+ export declare const validators: {
62
+ "<named-color>": (str: string) => boolean;
63
+ "<system-color>": (str: string) => boolean;
64
+ "<hex-color>": (str: string) => boolean;
65
+ "<number>": (str: string, args: string[] | undefined) => boolean;
66
+ "<percentage>": (str: string, args: string[] | undefined) => boolean;
67
+ "<angle>": (str: string, args: string[] | undefined) => boolean;
68
+ };
69
+ export declare const parsers: Record<string, (node: ParseTreeNode) => {
70
+ model: ColorModel;
71
+ coords: number[];
72
+ }>;
73
+ export declare const formatters: {
74
+ readonly "hex-color": {
75
+ readonly bridge: "rgb";
76
+ readonly fromBridge: (coords: number[]) => number[];
77
+ readonly format: ([r, g, b, a]: number[]) => string;
78
+ };
79
+ readonly "named-color": {
80
+ readonly bridge: "rgb";
81
+ readonly fromBridge: (coords: number[]) => number[];
82
+ readonly format: (rgb: number[]) => string | undefined;
83
+ };
84
+ readonly "device-cmyk": {
85
+ readonly bridge: "rgb";
86
+ readonly fromBridge: (coords: number[]) => number[];
87
+ readonly format: ([red, green, blue, alpha]: number[], options?: FormattingOptions) => string;
88
+ };
89
+ readonly "xyz-d65": ColorFormatter;
90
+ readonly rgb: ColorFormatter;
91
+ readonly "xyz-d50": ColorFormatter;
92
+ readonly lab: ColorFormatter;
93
+ readonly oklab: ColorFormatter;
94
+ readonly srgb: ColorFormatter;
95
+ readonly "srgb-linear": ColorFormatter;
96
+ readonly "display-p3": ColorFormatter;
97
+ readonly rec2020: ColorFormatter;
98
+ readonly "a98-rgb": ColorFormatter;
99
+ readonly "prophoto-rgb": ColorFormatter;
100
+ readonly xyz: ColorFormatter;
101
+ readonly hsl: ColorFormatter;
102
+ readonly hwb: ColorFormatter;
103
+ readonly lch: ColorFormatter;
104
+ readonly oklch: ColorFormatter;
105
+ };
106
+ export declare function parseNode(node: ParseTreeNode): {
107
+ model: ColorModel;
108
+ coords: number[];
109
+ };
110
+ export declare function isValid(input: string, rule?: string): boolean;
111
+ declare const getters: {
112
+ readonly "color-models": () => ColorModel[];
113
+ readonly "color-spaces": () => ColorSpace[];
114
+ readonly "named-colors": () => NamedColor[];
115
+ readonly "system-colors": () => SystemColor[];
116
+ readonly "output-types": () => OutputType[];
117
+ readonly plugins: () => string[];
118
+ readonly "fit-methods": () => FitMethod[];
119
+ };
120
+ type Getter = keyof typeof getters;
121
+ /**
122
+ * Retrieve a list of registered items of a specified type.
123
+ *
124
+ * @param type - The getter type, e.g. "color-types".
125
+ * @returns The array returned by the getter.
126
+ */
127
+ export declare function get<T extends Getter>(type: T): ReturnType<(typeof getters)[T]>;
128
+ declare const registerers: {
129
+ readonly "named-color": (name: string, rgb: number[]) => void;
130
+ readonly "color-function": (name: string, converter: ColorModelConverter) => void;
131
+ readonly "color-space": (name: string, converter: ColorSpaceConverter) => void;
132
+ readonly parser: (name: string, spec: GrammarRuleSpec) => void;
133
+ readonly formatter: (name: string, converter: ColorFormatter) => void;
134
+ readonly "fit-method": (name: string, method: FitFunction) => void;
135
+ };
136
+ type RegistererType = keyof typeof registerers;
137
+ type RegistererEntry<T extends RegistererType> = {
138
+ name: string;
139
+ value: Parameters<(typeof registerers)[T]>[1];
140
+ };
141
+ /**
142
+ * Bulk register multiple converters of a specified type.
143
+ *
144
+ * @param type - The converter type, e.g. "color-function".
145
+ * @param entries - Array of { name, value } objects.
146
+ */
147
+ export declare function register<T extends RegistererType>(type: T, entries: RegistererEntry<T>[]): void;
148
+ export {};