Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | 2x 319x 7170x 1639x 5531x 5603x 1641x 1641x 7170x 7170x 7170x 7242x 653x 7242x 1641x 1641x 1638x 3x 3x 3x 3x 56x 54x 52x 3x 3x 3x 3x 1638x 1638x 1x 1637x 1637x 315x 1322x 1641x 1641x 1641x 3x 3x 1638x 1638x 316x 1x 316x 1641x 167x 1641x 1500x 1641x 62x 13x 62x 1641x 2x 32x 32x 1643x 1643x 2x 1641x 1641x 1641x 32x 2x 11x 4x 4x 2x | import StyleDictionary from 'style-dictionary';
import { MODE_EXTENSION_KEY } from '../utils/constants.js';
/**
* Custom format for Design Token raw format
* Outputs to {theme}.hooks.raw.json
*/
const LIGHT_DARK_PREFIX = 'light-dark(';
/**
* Create the standard dark mode extension structure
* Single source of truth for the $extensions["com.salesforce-ux.mode"].dark format
* @param {*} darkValue - The dark mode value
* @returns {object} - Extension object in DTCG format
*/
function createDarkModeExtension(darkValue) {
return { [MODE_EXTENSION_KEY]: { dark: { $value: darkValue } } };
}
/**
* Split camelCase string into separate words
* @param {string} str - camelCase string (e.g., "electricBlue")
* @returns {string[]} - Array of words (e.g., ["electric", "blue"])
*/
function splitCamelCase(str) {
// Handle empty or single character strings
if (!str || str.length <= 1) {
return [str];
}
// Split on capital letters, but keep the capital letters with their following lowercase letters
// Example: "electricBlue" -> ["electric", "Blue"] -> ["electric", "blue"]
const parts = str.split(/(?=[A-Z])/);
return parts.map((s) => s.charAt(0).toLowerCase() + s.slice(1));
}
/**
* Navigate to a nested path in the tokens object, creating intermediate objects as needed
* Splits camelCase path segments into separate nested levels
* @param {object} tokens - Root tokens object
* @param {string[]} path - Token path array
* @returns {object} - The parent object where the token should be placed
*/
function navigateToPath(tokens, path) {
let current = tokens;
for (let i = 0; i < path.length - 1; i++) {
const segment = path[i];
// Split camelCase segments into separate levels
// e.g., "electricBlue" becomes ["electric", "blue"]
const segments = splitCamelCase(segment);
for (const subSegment of segments) {
if (!current[subSegment]) {
current[subSegment] = {};
}
current = current[subSegment];
}
}
return current;
}
/**
* Parse a light-dark() CSS function and extract light/dark values
* @param {string} value - CSS value potentially containing light-dark()
* @returns {object|null} - { light, dark } values or null if not a light-dark() function
*/
function parseLightDarkValue(value) {
if (typeof value !== 'string' || !value.startsWith(LIGHT_DARK_PREFIX)) {
return null;
}
// Remove 'light-dark(' prefix and trailing ')'
const content = value.slice(LIGHT_DARK_PREFIX.length, -1);
// Find the comma that separates light and dark values (not inside nested var parens)
let depth = 0;
let splitIndex = -1;
for (let i = 0; i < content.length; i++) {
if (content[i] === '(') depth++;
else if (content[i] === ')') depth--;
else if (content[i] === ',' && depth === 0) {
splitIndex = i;
break;
}
}
Iif (splitIndex === -1) {
return null;
}
return {
light: content.slice(0, splitIndex).trim(),
dark: content.slice(splitIndex + 1).trim(),
};
}
/**
* Extract dark mode extension from token's original value or extensions
* Only called if parseLightDarkValue didn't find a light-dark() function
* @param {object} token - Style Dictionary token
* @returns {object|null} - { lightValue, darkExtension } or null
*/
function extractDarkModeExtension(token) {
// Check for light/dark object format in original value
const originalValue = token.original?.$value;
if (originalValue && typeof originalValue === 'object' && originalValue.light && originalValue.dark) {
return {
lightValue: originalValue.light,
darkExtension: createDarkModeExtension(originalValue.dark),
};
}
// Check for dark mode in $extensions
const darkValue = token.original?.$extensions?.[MODE_EXTENSION_KEY]?.dark?.$value;
if (darkValue) {
return {
lightValue: null, // Keep existing $value unchanged
darkExtension: createDarkModeExtension(darkValue),
};
}
return null;
}
/**
* Build a DTCG-compliant token object
* @param {object} token - Style Dictionary token
* @param {*} tokenValue - Pre-computed token value (original or computed)
* @returns {object} - Token object with $type, $value, and optional metadata
*/
function buildTokenObject(token, tokenValue) {
const tokenObject = {
$type: token.$type,
$value: tokenValue,
};
// Parse light-dark() CSS function first
const lightDark = parseLightDarkValue(tokenValue);
if (lightDark) {
tokenObject.$value = lightDark.light;
tokenObject.$extensions = createDarkModeExtension(lightDark.dark);
} else {
// Only check other dark mode sources if not a light-dark() function
const darkMode = extractDarkModeExtension(token);
if (darkMode) {
if (darkMode.lightValue !== null) {
tokenObject.$value = darkMode.lightValue;
}
tokenObject.$extensions = darkMode.darkExtension;
}
}
// Add deprecated flag
if (token.$deprecated || token.original?.$deprecated) {
tokenObject.$deprecated = true;
}
// Add description
if (token.$description) {
tokenObject.$description = token.$description;
}
// Add cssProperties from $extensions if present
if (token.original?.$extensions?.cssProperties) {
if (!tokenObject.$extensions) {
tokenObject.$extensions = {};
}
tokenObject.$extensions.cssProperties = token.original.$extensions.cssProperties;
}
return tokenObject;
}
// ============================================================================
// MAIN FORMATTER
// ============================================================================
const jsonRawFormatter = (dictionary) => {
const tokens = {};
dictionary.allTokens.forEach((token) => {
const tokenValue = token.original?.$value || token.value;
// Skip tokens without values
if (tokenValue === undefined || tokenValue === null) {
return;
}
const current = navigateToPath(tokens, token.path);
const tokenName = token.path[token.path.length - 1];
current[tokenName] = buildTokenObject(token, tokenValue);
});
return JSON.stringify(tokens, null, 2);
};
/**
* Style Dictionary registration for the JSON raw format
*
* @param {StyleDictionary} StyleDictionary
*/
export const jsonRawFormat = (StyleDictionary) => {
const formatter = ({ dictionary }) => jsonRawFormatter(dictionary);
// Avoid nested collision warnings - this formatter creates a nested structure
formatter.nested = true;
StyleDictionary.registerFormat({
name: 'json/raw',
format: formatter,
});
};
// Export internal functions for testing
export const _testExports = {
jsonRawFormatter,
};
|