@terrazzo/token-tools
Version:
Various utilities for token types
354 lines (351 loc) • 6.54 kB
JavaScript
import "culori/css";
import { formatHex, parse } from "culori/fn";
//#region src/color.ts
const HEX_RE = /^
const CULORI_TO_CSS = {
a98: "a98-rgb",
hsl: "hsl",
hwb: "hwb",
lab: "lab",
lab65: "lab-d65",
lch: "lch",
lrgb: "srgb-linear",
oklab: "oklab",
oklch: "oklch",
okhsv: "okhsv",
p3: "display-p3",
prophoto: "prophoto-rgb",
rec2020: "rec2020",
rgb: "srgb",
xyz50: "xyz-d50",
xyz65: "xyz-d65"
};
const CSS_TO_CULORI = {
"a98-rgb": "a98",
"display-p3": "p3",
hsl: "hsl",
hwb: "hwb",
lab: "lab",
"lab-d65": "lab65",
lch: "lch",
oklab: "oklab",
oklch: "oklch",
okhsv: "okhsv",
"prophoto-rgb": "prophoto",
rec2020: "rec2020",
srgb: "rgb",
"srgb-linear": "lrgb",
xyz: "xyz65",
"xyz-d50": "xyz50",
"xyz-d65": "xyz65"
};
/** Parse any color */
function parseColor(color) {
const result = parse(color);
if (!result) throw new Error(`Unable to parse color "${color}"`);
if (!(result.mode in CULORI_TO_CSS)) throw new Error(`Unsupported color space: ${result.mode}`);
const colorSpace = CULORI_TO_CSS[result.mode];
let components = [
0,
0,
0
];
switch (result.mode) {
case "a98":
case "rec2020":
case "p3":
case "prophoto":
case "lrgb":
case "rgb": {
components = [
result.r,
result.g,
result.b
];
break;
}
case "hsl": {
const maxS = COLORSPACE[colorSpace].ranges[1]?.[1] ?? 1;
const maxL = COLORSPACE[colorSpace].ranges[2]?.[1] ?? 1;
components = [
result.h ?? 0,
result.s * maxS,
result.l * maxL
];
break;
}
case "hwb": {
const maxW = COLORSPACE[colorSpace].ranges[1]?.[1] ?? 1;
const maxB = COLORSPACE[colorSpace].ranges[2]?.[1] ?? 1;
components = [
result.h ?? 0,
result.w * maxW,
result.b * maxB
];
break;
}
case "lab":
case "lab65":
case "oklab": {
components = [
result.l,
result.a,
result.b
];
break;
}
case "lch":
case "oklch": {
components = [
result.l,
result.c,
result.h ?? 0
];
break;
}
case "okhsv": {
components = [
result.h ?? 0,
result.s,
result.v
];
break;
}
case "xyz50":
case "xyz65": {
components = [
result.x,
result.y,
result.z
];
break;
}
}
const value = {
colorSpace,
components,
alpha: result.alpha ?? 1
};
if (HEX_RE.test(color)) value.hex = formatHex(result);
return value;
}
/** Convert a color token to a Culori color */
function tokenToCulori(value) {
switch (value.colorSpace) {
case "a98-rgb":
case "display-p3":
case "prophoto-rgb":
case "rec2020":
case "srgb":
case "srgb-linear": {
const [r, g, b] = value.components;
return {
mode: CSS_TO_CULORI[value.colorSpace] || value.colorSpace,
r,
g,
b,
alpha: value.alpha
};
}
case "hsl": {
const [h, s, l] = value.components;
const maxS = COLORSPACE[value.colorSpace].ranges[1]?.[1] ?? 1;
const maxL = COLORSPACE[value.colorSpace].ranges[2]?.[1] ?? 1;
return {
mode: "hsl",
h,
s: s / maxS,
l: l / maxL,
alpha: value.alpha
};
}
case "hwb": {
const [h, w, b] = value.components;
const maxW = COLORSPACE[value.colorSpace].ranges[1]?.[1] ?? 1;
const maxB = COLORSPACE[value.colorSpace].ranges[2]?.[1] ?? 1;
return {
mode: "hwb",
h,
w: w / maxW,
b: b / maxB,
alpha: value.alpha
};
}
case "lab":
case "lab-d65":
case "oklab": {
const [l, a, b] = value.components;
const mode = value.colorSpace === "lab-d65" ? "lab65" : value.colorSpace;
return {
mode,
l,
a,
b,
alpha: value.alpha
};
}
case "lch":
case "oklch": {
const [l, c, h] = value.components;
return {
mode: value.colorSpace,
l,
c,
h,
alpha: value.alpha
};
}
case "okhsv": {
const [h, s, v] = value.components;
return {
mode: value.colorSpace,
h,
s,
v,
alpha: value.alpha
};
}
case "xyz":
case "xyz-d50":
case "xyz-d65": {
const [x, y, z] = value.components;
return {
mode: CSS_TO_CULORI[value.colorSpace],
x,
y,
z,
alpha: value.alpha
};
}
default: throw new Error(`Invalid colorSpace "${value.colorSpace}". Expected one of ${Object.keys(CSS_TO_CULORI).join(", ")}`);
}
}
/** Complete list of CSS Module 4 Colorspaces */
const COLORSPACE = {
"a98-rgb": { ranges: [
[],
[],
[]
] },
"display-p3": { ranges: [
[],
[],
[]
] },
hsl: { ranges: [
[],
[],
[]
] },
hwb: { ranges: [
[],
[],
[]
] },
lab: { ranges: [
[],
[-125, 125],
[-125, 125]
] },
"lab-d65": { ranges: [
[],
[-125, 125],
[-125, 125]
] },
lch: { ranges: [
[],
[],
[]
] },
oklab: { ranges: [
[],
[-.4, .4],
[-.4, .4]
] },
oklch: { ranges: [
[],
[],
[]
] },
okhsv: { ranges: [
[],
[],
[]
] },
"prophoto-rgb": { ranges: [
[],
[],
[]
] },
rec2020: { ranges: [
[],
[],
[]
] },
srgb: { ranges: [
[],
[],
[]
] },
"srgb-linear": { ranges: [
[],
[],
[]
] },
"xyz-d50": { ranges: [
[],
[],
[]
] },
xyz: { ranges: [
[],
[],
[]
] },
"xyz-d65": { ranges: [
[],
[],
[]
] }
};
//#endregion
//#region src/string.ts
/** Pad string lengths */
function padStr(input, length, alignment = "left") {
const d = Math.min(length || 0, 1e3) - input.length;
if (d > 0) switch (alignment) {
case "left": return `${input}${" ".repeat(d)}`;
case "right": return `${" ".repeat(d)}${input}`;
case "center": {
const left = Math.floor(d / 2);
const right = d - left;
return `${" ".repeat(left)}${input}${" ".repeat(right)}`;
}
}
return input;
}
/** Pluralize strings */
function pluralize(count, singular, plural) {
return count === 1 ? singular : plural;
}
/** Turn a string into kebab-case */
function kebabCase(str) {
let output = "";
for (const c of str.split("")) {
if (c === ".") {
output += "-";
continue;
}
let isFirstUppercase = true;
if (isFirstUppercase && /\p{Uppercase_Letter}/u.test(c)) {
output += `-${c.toLocaleLowerCase()}`;
isFirstUppercase = false;
continue;
} else isFirstUppercase = true;
output += c;
}
return output;
}
//#endregion
export { COLORSPACE, CSS_TO_CULORI, CULORI_TO_CSS, kebabCase, padStr, parseColor, pluralize, tokenToCulori };
//# sourceMappingURL=string-BwN3ybNv.js.map