UNPKG

@terrazzo/token-tools

Version:
354 lines (351 loc) 6.54 kB
import "culori/css"; import { formatHex, parse } from "culori/fn"; //#region src/color.ts const HEX_RE = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; 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: [ [0, 1], [0, 1], [0, 1] ] }, "display-p3": { ranges: [ [0, 1], [0, 1], [0, 1] ] }, hsl: { ranges: [ [0, 360], [0, 100], [0, 100] ] }, hwb: { ranges: [ [0, 360], [0, 100], [0, 100] ] }, lab: { ranges: [ [0, 100], [-125, 125], [-125, 125] ] }, "lab-d65": { ranges: [ [0, 100], [-125, 125], [-125, 125] ] }, lch: { ranges: [ [0, 100], [0, 150], [0, 360] ] }, oklab: { ranges: [ [0, 1], [-.4, .4], [-.4, .4] ] }, oklch: { ranges: [ [0, 1], [0, .4], [0, 360] ] }, okhsv: { ranges: [ [0, 360], [0, 1], [0, 1] ] }, "prophoto-rgb": { ranges: [ [0, 1], [0, 1], [0, 1] ] }, rec2020: { ranges: [ [0, 1], [0, 1], [0, 1] ] }, srgb: { ranges: [ [0, 1], [0, 1], [0, 1] ] }, "srgb-linear": { ranges: [ [0, 1], [0, 1], [0, 1] ] }, "xyz-d50": { ranges: [ [0, 1], [0, 1], [0, 1] ] }, xyz: { ranges: [ [0, 1], [0, 1], [0, 1] ] }, "xyz-d65": { ranges: [ [0, 1], [0, 1], [0, 1] ] } }; //#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