UNPKG

@terrazzo/token-tools

Version:
358 lines (339 loc) 15.9 kB
import { CSS_TO_CULORI, kebabCase, parseColor, tokenToCulori } from "./string-BwN3ybNv.js"; import { displayable, formatCss, formatHex, formatHex8, modeA98, modeHsl, modeHwb, modeLab, modeLab65, modeLch, modeLrgb, modeOkhsv, modeOklab, modeOklch, modeP3, modeProphoto, modeRec2020, modeRgb, modeXyz50, modeXyz65, toGamut, useMode } from "culori/fn"; import { displayable as displayable$1 } from "culori"; //#region src/css/lib.ts function defaultAliasTransform(token) { if (!token) throw new Error("Undefined token"); return `var(${makeCSSVar(token.id)})`; } /** Generate shorthand CSS for select token types */ function generateShorthand({ $type, localID }) { switch ($type) { case "transition": return [ "duration", "delay", "timing-function" ].map((p) => makeCSSVar(`${localID}-${p}`, { wrapVar: true })).join(" "); } } const CSS_VAR_RE = /(?:(\p{Uppercase_Letter}?[\p{Lowercase_Letter}\p{Number}]+|[\p{Uppercase_Letter}\p{Number}]+|[\u{80}-\u{10FFFF}\p{Number}]+)|.)/u; /** * Generate a valid CSS variable from any string * Code by @dfrankland */ function makeCSSVar(name, { prefix, wrapVar = false } = {}) { if (typeof name !== "string") throw new Error(`makeCSSVar() Expected string, received ${name}`); let property = name.split(CSS_VAR_RE).filter(Boolean).join("-"); if (prefix && !property.startsWith(`${prefix}-`)) property = `${prefix}-${property}`; const finalProperty = `--${property}`.toLocaleLowerCase(); return wrapVar ? `var(${finalProperty})` : finalProperty; } //#endregion //#region src/css/boolean.ts /** Convert boolean value to CSS string */ function transformBoolean(token, { tokensSet, transformAlias = defaultAliasTransform }) { if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return token.$value === true ? "1" : "0"; } //#endregion //#region src/css/color.ts /** Convert color value to CSS string */ function transformColor(token, options) { const { transformAlias = defaultAliasTransform, tokensSet } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); const { colorSpace, components, alpha = 1 } = typeof token.$value === "string" ? parseColor(token.$value) : token.$value; const color = tokenToCulori({ colorSpace, components, alpha }); if (!color) throw new Error(`Can’t convert color ${JSON.stringify(token.$value)} to Culori color`); let formatColor = formatCss; if (options.color?.legacyHex) formatColor = color.alpha !== 1 ? formatHex8 : formatHex; return displayable(color) ? formatColor(color) : downsample({ colorSpace, components, alpha }, color); } const converters = { a98: useMode(modeA98), hsl: useMode(modeHsl), hwb: useMode(modeHwb), lab: useMode(modeLab), lab65: useMode(modeLab65), lch: useMode(modeLch), lrgb: useMode(modeLrgb), oklab: useMode(modeOklab), oklch: useMode(modeOklch), okhsv: useMode(modeOkhsv), p3: useMode(modeP3), prophoto: useMode(modeProphoto), rec2020: useMode(modeRec2020), rgb: useMode(modeRgb), xyz50: useMode(modeXyz50), xyz65: useMode(modeXyz65) }; /** * Downsample color to sRGB/Display P3/Rec2020 colorspaces. * Note: because Culori tends to convert to RGB color spaces to ensure the operation, * we have to do an additional step of converting back. So we’re not really converting; * we’re just preserving the original colorspace. */ function downsample($value, culoriColor) { if (!($value.colorSpace in CSS_TO_CULORI)) throw new Error(`Invalid colorSpace "${$value.colorSpace}". Expected one of: ${Object.keys(CSS_TO_CULORI).join(", ")}`); const conversionSpace = CSS_TO_CULORI[$value.colorSpace] || "oklab"; let gamutSpace = conversionSpace; if (gamutSpace === "lab" || gamutSpace === "lab65") gamutSpace = "lch"; else if (gamutSpace === "oklab") gamutSpace = "oklch"; const srgb = converters[conversionSpace](toGamut("rgb", gamutSpace)(culoriColor)); const p3 = converters[conversionSpace](toGamut("p3", gamutSpace)(culoriColor)); const rec2020 = converters[conversionSpace](toGamut("rec2020", gamutSpace)(culoriColor)); return { ".": formatCss(culoriColor), srgb: formatCss(srgb), p3: formatCss(p3), rec2020: formatCss(rec2020) }; } //#endregion //#region src/css/dimension.ts /** Convert dimension value to CSS */ function transformDimension(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return token.$value.value === 0 ? "0" : `${token.$value.value}${token.$value.unit}`; } //#endregion //#region src/css/stroke-style.ts /** Convert strokeStyle value to CSS */ function transformStrokeStyle(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return typeof token.$value === "string" ? token.$value : "dashed"; } //#endregion //#region src/css/border.ts /** Convert border value to multiple CSS values */ function transformBorder(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); const width = token.partialAliasOf?.width ? transformAlias(tokensSet[token.partialAliasOf.width]) : transformDimension({ $value: token.$value.width }, options); const color = token.partialAliasOf?.color ? transformAlias(tokensSet[token.partialAliasOf.color]) : transformColor({ $value: token.$value.color }, options); const style = token.partialAliasOf?.style ? transformAlias(tokensSet[token.partialAliasOf.style]) : transformStrokeStyle({ $value: token.$value.style }, options); const formatBorder = (colorKey) => [ width, style, typeof color === "string" ? color : color[colorKey] ].join(" "); return typeof color === "string" || displayable$1(color.p3) ? formatBorder(".") : { ".": formatBorder("."), srgb: formatBorder("srgb"), p3: formatBorder("p3"), rec2020: formatBorder("rec2020") }; } //#endregion //#region src/css/cubic-bezier.ts /** Convert cubicBezier value to CSS */ function transformCubicBezier(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return `cubic-bezier(${token.$value.map((v, i) => token.partialAliasOf?.[i] ? transformAlias(tokensSet[token.partialAliasOf[i]]) : v).join(", ")})`; } //#endregion //#region src/css/duration.ts /** Convert duration value to CSS */ function transformDuration(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return `${token.$value.value}${token.$value.unit}`; } //#endregion //#region src/css/font-family.ts const FONT_NAME_KEYWORD = /^[a-z-]+$/; function transformFontFamily(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return token.$value.map((fontName) => FONT_NAME_KEYWORD.test(fontName) ? fontName : `"${fontName}"`).join(", "); } //#endregion //#region src/css/font-weight.ts /** Convert fontWeight value to CSS */ function transformFontWeight(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return String(token.$value); } //#endregion //#region src/css/gradient.ts /** Convert gradient value to CSS */ function transformGradient(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); let isHDR = false; const colors = []; const positions = []; for (let i = 0; i < token.$value.length; i++) { const { color, position } = token.$value[i]; const colorValue = token.partialAliasOf?.[i]?.color ? transformAlias(tokensSet[token.partialAliasOf[i].color]) : transformColor({ $value: color }, options); if (typeof colorValue !== "string") isHDR = true; colors.push(colorValue); positions.push(token.partialAliasOf?.[i]?.position ? transformAlias(tokensSet[token.partialAliasOf[i].position]) : `${100 * position}%`); } function formatStop(i, colorKey = ".") { return `${typeof colors[i] === "string" ? colors[i] : colors[i][colorKey]} ${positions[i]}`; } return !isHDR ? token.$value.map((_, i) => formatStop(i, positions[i])).join(", ") : { ".": token.$value.map((_, i) => formatStop(i, ".")).join(", "), srgb: token.$value.map((_, i) => formatStop(i, "srgb")).join(", "), p3: token.$value.map((_, i) => formatStop(i, "p3")).join(", "), rec2020: token.$value.map((_, i) => formatStop(i, "rec2020")).join(", ") }; } //#endregion //#region src/css/link.ts /** Convert link value to CSS */ function transformLink(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return `url("${token.$value}")`; } //#endregion //#region src/css/number.ts /** Convert number value to CSS */ function transformNumber(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return String(token.$value); } //#endregion //#region src/css/shadow.ts /** Convert shadow subvalue to CSS */ function transformShadowLayer(value, options) { const { tokensSet, colorValue, partialAliasOf, transformAlias = defaultAliasTransform } = options; const offsetX = partialAliasOf?.offsetX ? transformAlias(tokensSet[partialAliasOf.offsetX]) : transformDimension({ $value: value.offsetX }, options); const offsetY = partialAliasOf?.offsetY ? transformAlias(tokensSet[partialAliasOf.offsetY]) : transformDimension({ $value: value.offsetY }, options); const blur = partialAliasOf?.blur ? transformAlias(tokensSet[partialAliasOf.blur]) : transformDimension({ $value: value.blur }, options); const spread = partialAliasOf?.spread ? transformAlias(tokensSet[partialAliasOf.spread]) : transformDimension({ $value: value.spread }, options); const inset = value?.inset === true ? "inset" : void 0; return [ inset, offsetX, offsetY, blur, spread, colorValue ].filter(Boolean).join(" "); } /** Convert shadow value to CSS */ function transformShadow(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); const colors = token.$value.map((v, i) => token.partialAliasOf?.[i]?.color ? transformAlias(tokensSet[token.partialAliasOf[i].color]) : transformColor({ $value: v.color }, options)); const isHDR = colors.some((c) => typeof c === "object"); const formatShadow = (colorKey) => token.$value.map((v, i) => transformShadowLayer(v, { tokensSet, colorValue: typeof colors[i] === "string" ? colors[i] : colors[i][colorKey], partialAliasOf: token.partialAliasOf?.[i], transformAlias })).join(", "); return !isHDR ? formatShadow(".") : { ".": formatShadow("."), srgb: formatShadow("srgb"), p3: formatShadow("p3"), rec2020: formatShadow("rec2020") }; } //#endregion //#region src/css/string.ts /** Convert string value to CSS */ function transformString(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); return String(token.$value); } //#endregion //#region src/css/transition.ts /** Convert transition value to shorthand */ function transformTransition(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; if (token.aliasChain?.[0]) return transformAlias(tokensSet[token.aliasChain[0]]); const duration = token.partialAliasOf?.duration ? transformAlias(tokensSet[token.partialAliasOf.duration]) : transformDuration({ $value: token.$value.duration }, options); const delay = token.partialAliasOf?.delay ? transformAlias(tokensSet[token.partialAliasOf.delay]) : transformDuration({ $value: token.$value.delay }, options); const timingFunction = token.partialAliasOf?.timingFunction ? transformAlias(tokensSet[token.partialAliasOf.timingFunction]) : transformCubicBezier({ $value: token.$value.timingFunction }, options); return `${duration} ${delay} ${timingFunction}`; } //#endregion //#region src/css/typography.ts /** Convert typography value to multiple CSS values */ function transformTypography(token, options) { const { tokensSet, transformAlias = defaultAliasTransform } = options; const output = {}; for (const [property, subvalue] of Object.entries(token.$value)) { let transformedValue; const aliasedID = token.aliasChain?.[0] ?? token.partialAliasOf?.[property]; if (aliasedID) { const resolvedToken = tokensSet[aliasedID]; transformedValue = transformAlias(resolvedToken.$type === "typography" ? { id: `${aliasedID}-${property}` } : resolvedToken); } else switch (property) { case "fontFamily": { transformedValue = transformFontFamily({ $value: subvalue }, options); break; } case "fontSize": case "letterSpacing": { transformedValue = transformDimension({ $value: subvalue }, options); break; } case "fontWeight": { transformedValue = transformFontWeight({ $value: subvalue }, options); break; } case "lineHeight": { if (typeof subvalue === "number") transformedValue = transformNumber({ $value: subvalue }, options); else transformedValue = transformDimension({ $value: subvalue }, options); break; } default: { transformedValue = transformString({ $value: subvalue }, options); break; } } output[kebabCase(property)] = transformedValue; } return output; } //#endregion //#region src/css/index.ts /** Main CSS Transform */ function transformCSSValue(token, { mode,...options }) { const selectedMode = token.mode[mode]; if (!selectedMode) return; const tokenWithModeValue = { id: token.id, $type: token.$type, ...selectedMode }; switch (tokenWithModeValue.$type) { case "boolean": return transformBoolean(tokenWithModeValue, options); case "border": return transformBorder(tokenWithModeValue, options); case "color": return transformColor(tokenWithModeValue, options); case "cubicBezier": return transformCubicBezier(tokenWithModeValue, options); case "dimension": return transformDimension(tokenWithModeValue, options); case "duration": return transformDuration(tokenWithModeValue, options); case "fontFamily": return transformFontFamily(tokenWithModeValue, options); case "fontWeight": return transformFontWeight(tokenWithModeValue, options); case "gradient": return transformGradient(tokenWithModeValue, options); case "link": return transformLink(tokenWithModeValue, options); case "number": return transformNumber(tokenWithModeValue, options); case "shadow": return transformShadow(tokenWithModeValue, options); case "string": return transformString(tokenWithModeValue, options); case "strokeStyle": return transformStrokeStyle(tokenWithModeValue, options); case "transition": return transformTransition(tokenWithModeValue, options); case "typography": return transformTypography(tokenWithModeValue, options); } } //#endregion export { defaultAliasTransform, generateShorthand, makeCSSVar, transformBoolean, transformBorder, transformCSSValue, transformColor, transformCubicBezier, transformDimension, transformDuration, transformFontFamily, transformFontWeight, transformGradient, transformLink, transformNumber, transformShadow, transformShadowLayer, transformString, transformStrokeStyle, transformTransition, transformTypography }; //# sourceMappingURL=css.js.map