import { TokenState } from '@specifyapp/specify-design-token-format'; import type { ToCssCustomPropertiesParserOptions } from './definition.js'; import { makeCSSVariable } from './internals/makeCSSVariable.js'; import { dimensionToCss, dimensionValueToCss, } from '../../../converters/css/tokenTypes/dimension.js'; import { colorToCss, colorValueToCss } from '../../../converters/css/tokenTypes/color.js'; import { cubicBezierToCss, cubicBezierValueToCss, } from '../../../converters/css/tokenTypes/cubicBezier.js'; import { durationToCss, durationValueToCss } from '../../../converters/css/tokenTypes/duration.js'; import { breakpointToCss, breakpointValueToCss, } from '../../../converters/css/tokenTypes/breakpoint.js'; import { borderToCss, borderValueToCss } from '../../../converters/css/tokenTypes/border.js'; import { gradientToCss, gradientValueToCss } from '../../../converters/css/tokenTypes/gradient.js'; import { gradientsToCss, gradientsValueToCss, } from '../../../converters/css/tokenTypes/gradients.js'; import { fontToCss, fontValueToCss } from '../../../converters/css/tokenTypes/font.js'; import { fontWeightToCss, fontWeightValueToCss, } from '../../../converters/css/tokenTypes/fontWeight.js'; import { zIndexToCss, zIndexValueToCss } from '../../../converters/css/tokenTypes/zIndex.js'; import { opacityToCss, opacityValueToCss } from '../../../converters/css/tokenTypes/opacity.js'; import { radiusToCss, radiusValueToCss } from '../../../converters/css/tokenTypes/radius.js'; import { radiiToCss, radiiValueToCss } from '../../../converters/css/tokenTypes/radii.js'; import { shadowToCss, shadowValueToCss } from '../../../converters/css/tokenTypes/shadow.js'; import { shadowsToCss, shadowsValueToCss } from '../../../converters/css/tokenTypes/shadows.js'; import { spacingToCss, spacingValueToCss } from '../../../converters/css/tokenTypes/spacing.js'; import { spacingsToCss, spacingsValueToCss } from '../../../converters/css/tokenTypes/spacings.js'; import { textStyleToCss, textStyleValueToCss, } from '../../../converters/css/tokenTypes/textStyle.js'; import { transitionToCss, transitionValueToCss, } from '../../../converters/css/tokenTypes/transition.js'; import { stepsTimingFunctionToCss, stepsTimingFunctionValueToCss, } from '../../../converters/css/tokenTypes/stepsTimingFunction.js'; import { createAliasToVarStrategy } from '../../../converters/css/aliasStrategies/resolvableToVar.js'; import { createUnresolvableAliasToVarStrategy } from '../../../converters/css/aliasStrategies/unresolvableToVar.js'; import { createThrowOnUnresolvableStrategy } from '../../../converters/css/aliasStrategies/throwOnUnresolvable.js'; import { ParserToolbox } from '../../../parsersEngine/ParserToolbox.js'; import { doesTokenBelongToSameCollection } from './internals/doesTokenBelongToSameCollection.js'; import { DEFAULT_TOKEN_TEMPLATE, DEFAULT_TOKEN_NOT_IN_COLLECTION_TEMPLATE, renderVariable as renderCssVariable, } from '../../../converters/css/utils/template.js'; import { dataOfToken, type TemplateRenderer } from '../../utils/template.js'; export function renderTokenToCss( token: TokenState, parserOptions: ToCssCustomPropertiesParserOptions, toolbox: ParserToolbox, addedVariablesStorage: { [mode: string]: { [variable: string]: true } } = {}, ): { [mode: string]: string } | undefined { const allowUnresolvable = parserOptions?.allowUnresolvable ?? false; if (!allowUnresolvable && !token.isFullyResolvable) return undefined; const renderVariable: TemplateRenderer = data => renderCssVariable( data.collection ? parserOptions?.tokenNameTemplate ?? DEFAULT_TOKEN_TEMPLATE : parserOptions?.tokenNotInCollectionNameTemplate ?? DEFAULT_TOKEN_NOT_IN_COLLECTION_TEMPLATE, data, ); const aliasStrategy = createAliasToVarStrategy({ tokenNameTemplate: parserOptions?.tokenNameTemplate ?? DEFAULT_TOKEN_TEMPLATE, tokenNotInCollectionNameTemplate: parserOptions?.tokenNotInCollectionNameTemplate ?? DEFAULT_TOKEN_NOT_IN_COLLECTION_TEMPLATE, }); const unresolvableStrategy = allowUnresolvable ? createUnresolvableAliasToVarStrategy() : createThrowOnUnresolvableStrategy(); const tempOutput = token.matchByType< { [mode: string]: string | Record | number } | undefined >( { dimension: dimensionToCss(aliasStrategy, unresolvableStrategy), breakpoint: breakpointToCss(aliasStrategy, unresolvableStrategy), color: colorToCss(aliasStrategy, unresolvableStrategy), cubicBezier: cubicBezierToCss(aliasStrategy, unresolvableStrategy), duration: durationToCss(aliasStrategy, unresolvableStrategy), border: borderToCss(aliasStrategy, unresolvableStrategy), gradient: gradientToCss(aliasStrategy, unresolvableStrategy), gradients: gradientsToCss(aliasStrategy, unresolvableStrategy), font: fontToCss(aliasStrategy, unresolvableStrategy), fontWeight: fontWeightToCss(aliasStrategy, unresolvableStrategy), opacity: opacityToCss(aliasStrategy, unresolvableStrategy), radius: radiusToCss(aliasStrategy, unresolvableStrategy), radii: radiiToCss(aliasStrategy, unresolvableStrategy), shadow: shadowToCss(aliasStrategy, unresolvableStrategy), shadows: shadowsToCss(aliasStrategy, unresolvableStrategy), spacing: spacingToCss(aliasStrategy, unresolvableStrategy), spacings: spacingsToCss(aliasStrategy, unresolvableStrategy), textStyle: textStyleToCss(aliasStrategy, unresolvableStrategy), stepsTimingFunction: stepsTimingFunctionToCss(aliasStrategy, unresolvableStrategy), transition: transitionToCss(aliasStrategy, unresolvableStrategy), zIndex: zIndexToCss(aliasStrategy, unresolvableStrategy), }, _ => undefined, ); if (!tempOutput) return undefined; const outputByMode = Object.entries( tempOutput as { [mode: string]: string | Record }, ).reduce( (acc, [mode, value]) => { const variableName = renderVariable(dataOfToken(token, mode)); acc[mode] = typeof value === 'string' ? makeCSSVariable(variableName, value) : Object.entries(value) .map(([key, value]) => makeCSSVariable(`${variableName}-${key}`, value)) .join(''); return acc; }, {} as { [mode: string]: string }, ); if (!parserOptions?.includeCoreTokensInScopes) return outputByMode; const aliases = token.aliases; if (aliases.length === 0) return outputByMode; aliases.forEach(alias => { if (alias.status === 'unresolvable') return; if (doesTokenBelongToSameCollection(token, alias.tokenState)) return; const valueByMode = alias.tokenState.matchJSONValueByType< string | Record | number >( { dimension: dimensionValueToCss, breakpoint: breakpointValueToCss, color: colorValueToCss, cubicBezier: cubicBezierValueToCss, duration: durationValueToCss, border: borderValueToCss, gradient: gradientValueToCss, gradients: gradientsValueToCss, font: fontValueToCss, fontWeight: fontWeightValueToCss, opacity: opacityValueToCss, radius: radiusValueToCss, radii: radiiValueToCss, shadow: shadowValueToCss, shadows: shadowsValueToCss, spacing: spacingValueToCss, spacings: spacingsValueToCss, textStyle: textStyleValueToCss, stepsTimingFunction: stepsTimingFunctionValueToCss, transition: transitionValueToCss, zIndex: zIndexValueToCss, }, () => undefined, ); if (!valueByMode) return; const formattedValueByMode = Object.entries(valueByMode).reduce( (acc, [mode, value]) => { const variableName = renderVariable(dataOfToken(alias.tokenState, mode)); acc[mode] = typeof value === 'string' ? makeCSSVariable(variableName, value) : Object.entries(value) .map(([key, value]) => makeCSSVariable(`${variableName}-${key}`, value)) .join(''); return acc; }, {} as { [mode: string]: string }, ); if (alias.to.mode && alias.from.mode) { if (addedVariablesStorage[alias.from.mode]?.[formattedValueByMode[alias.to.mode]]) { return; } else { addedVariablesStorage[alias.from.mode] ??= {}; addedVariablesStorage[alias.from.mode][formattedValueByMode[alias.to.mode]] = true; } outputByMode[alias.from.mode] = outputByMode[alias.from.mode] + formattedValueByMode[alias.to.mode]; } else { Object.entries(outputByMode).forEach(([mode, value]) => { if (addedVariablesStorage[mode]?.[formattedValueByMode[mode]]) { return; } else { addedVariablesStorage[mode] ??= {}; addedVariablesStorage[mode][formattedValueByMode[mode]] = true; } outputByMode[mode] = value + formattedValueByMode[mode]; }); } }); return outputByMode; }