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 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | 4x 4x 4x 26x 40333x 39353x 39353x 39353x 39353x 41871x 41871x 2519x 2519x 2519x 2519x 2519x 53137x 53137x 53125x 2530x 12x 50595x 2519x 2518x 41871x 41871x 31418x 31418x 4x 15137x 15137x 10473x 10473x 15137x 15137x 10473x 111338x 35064x 48326x 48326x 19633x 19633x 23646x 3336x 34004x 34004x 34004x 34004x 177x 177x 177x 177x 5750x 5573x 354x 177x 177x 177x 57416x 57416x 57416x 91420x 91420x 57416x 57416x 34004x 34004x 34004x 34004x 34004x 34004x 33825x 33825x 179x 177x 2x 57416x 20055x 21466x 21466x 21466x 21466x 10991x 21466x 21466x 4x 2005x 2005x 1982x 1982x 3962x 1981x 1981x | import cosmosTokens from '@salesforce-ux/design-tokens/dist/themes/cosmos/cosmos.global.tokens.flat.json' with { type: 'json' };
import lightningBlueTokens from '@salesforce-ux/design-tokens/dist/themes/lightning-blue/lightning-blue.global.tokens.flat.json' with { type: 'json' };
import tokensPackage from '@salesforce-ux/design-tokens/package.json' with { type: 'json' };
/**
* Build-time access to `@salesforce-ux/design-tokens`. The per-theme flat JSON
* holds every g/s/r hook with its description, deprecation, light/dark mode and
* density metadata, so the site reads it instead of parsing the token CSS. The
* package's `dist/` is built, not committed: a missing build fails the import.
*/
export type Theme = 'cosmos' | 'lightning-blue';
export type Mode = 'light' | 'dark';
export const THEMES: { value: Theme; label: string }[] = [
{ value: 'cosmos', label: 'Cosmos' },
{ value: 'lightning-blue', label: 'Lightning Blue' },
];
interface ExtensionValue {
$value: string;
/** `{slds.r.color.brand.40}` (another hook) or `{alias.palette.neutral.100}` (a palette step). */
reference?: string;
}
/** One entry of `<theme>.global.tokens.flat.json`; `name` has no leading `--`. */
export interface FlatToken {
name: string;
value: string;
originalValue?: string;
type?: string;
syntax?: string;
category: string;
scope: string;
namespace?: string;
description?: string;
inherits?: boolean;
deprecated?: boolean;
darkValue?: string;
themeOwned?: boolean;
cssProperties?: string[];
$extensions?: {
'com.salesforce-ux.mode'?: { light?: ExtensionValue; dark?: ExtensionValue };
'com.salesforce-ux.density'?: {
comfy?: ExtensionValue & { default?: boolean };
compact?: ExtensionValue;
};
};
}
export type TokenMap = Record<string, FlatToken>;
const TOKENS: Record<Theme, TokenMap> = {
cosmos: cosmosTokens as TokenMap,
'lightning-blue': lightningBlueTokens as TokenMap,
};
export const TOKENS_SOURCE = `${tokensPackage.name}@${tokensPackage.version}`;
export function getThemeTokens(theme: Theme): TokenMap {
return TOKENS[theme];
}
/** `--slds-g-color-surface-1` → the `slds-g-color-surface-1` entry. */
export function getToken(tokens: TokenMap, hook: string): FlatToken | undefined {
return tokens[hook.replace(/^--/, '')];
}
/**
* Replace every `light-dark(a, b)` in `value` with its `mode` branch. Arguments
* are split at the top-level comma, so nested calls (`color-mix(...)`, or a
* `light-dark()` inside a branch once references are resolved) stay intact and
* are resolved recursively. A malformed call is left as is.
*/
export function selectColorScheme(value: string, mode: Mode): string {
const open = 'light-dark(';
let out = '';
let pos = 0;
for (;;) {
const start = value.indexOf(open, pos);
if (start === -1) return out + value.slice(pos);
out += value.slice(pos, start);
let depth = 0;
let comma = -1;
let i = start + open.length;
for (; i < value.length; i++) {
const c = value[i];
if (c === '(') depth++;
else if (c === ')') {
if (depth === 0) break;
depth--;
} else if (c === ',' && depth === 0 && comma === -1) comma = i;
}
if (comma === -1 || i >= value.length) return out + value.slice(start);
const branch = mode === 'light' ? value.slice(start + open.length, comma) : value.slice(comma + 1, i);
out += selectColorScheme(branch.trim(), mode);
pos = i + 1;
}
}
/** `{slds.r.color.brand.40}` → `--slds-r-color-brand-40`; undefined for palette aliases. */
export function hookFromReference(reference: string | undefined): string | undefined {
const match = reference?.match(/^\{(slds\.[^}]+)\}$/);
return match ? `--${match[1].replace(/\./g, '-')}` : undefined;
}
const PALETTE_ALIAS = /^\{alias\.palette\.([a-zA-Z]+)\.(\d+)\}$/;
/** `{alias.palette.electricBlue.50}` → `Electric Blue 50`. */
export function paletteNameFromAlias(alias: string | undefined): string | undefined {
const match = alias?.match(PALETTE_ALIAS);
if (!match) return undefined;
const hue = match[1].replace(/([a-z])([A-Z])/g, '$1 $2');
return `${hue[0].toUpperCase()}${hue.slice(1)} ${match[2]}`;
}
/** `{alias.palette.electricBlue.50}` → `--slds-g-color-palette-electric-blue-50`. */
export function paletteHookFromAlias(alias: string | undefined): string | undefined {
const match = alias?.match(PALETTE_ALIAS);
if (!match) return undefined;
return `--slds-g-color-palette-${match[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}-${match[2]}`;
}
function modeExtension(token: FlatToken, mode: Mode): ExtensionValue | undefined {
return token.$extensions?.['com.salesforce-ux.mode']?.[mode];
}
/** Whether the hook declares separate light and dark values. */
export function hasModes(token: FlatToken): boolean {
return Boolean(modeExtension(token, 'light') && modeExtension(token, 'dark'));
}
/**
* The value a hook declares for `mode`, as written in the token CSS:
* `var(--slds-r-color-brand-40)` when the mode references another hook, the
* literal otherwise. Hooks without mode metadata declare `value` in both modes.
*/
export function modeDeclaration(token: FlatToken, mode: Mode): string {
const ext = modeExtension(token, mode);
if (!ext) return token.value;
const hook = hookFromReference(ext.reference);
return hook ? `var(${hook})` : ext.$value;
}
/**
* The hook's declaration as the token CSS writes it. For hooks with modes the
* flat JSON `value` is the light value only, so `light-dark()` is rebuilt from
* the mode references.
*/
export function cssDeclaration(token: FlatToken): string {
if (!hasModes(token)) return token.value;
return `light-dark(${modeDeclaration(token, 'light')}, ${modeDeclaration(token, 'dark')})`;
}
interface VarCall {
end: number;
name: string;
fallback: string | null;
}
/** Parse the `var(--name[, fallback])` call starting at `start`. */
function parseVarCall(value: string, start: number): VarCall | null {
const match = value.slice(start).match(/^var\(\s*(--[\w-]+)\s*/);
Iif (!match) return null;
let i = start + match[0].length;
if (value[i] === ')') return { end: i + 1, name: match[1], fallback: null };
Iif (value[i] !== ',') return null;
const fallbackStart = i + 1;
let depth = 0;
for (i = fallbackStart; i < value.length; i++) {
if (value[i] === '(') depth++;
else if (value[i] === ')') {
if (depth === 0) break;
depth--;
}
}
Iif (i >= value.length) return null;
return { end: i + 1, name: match[1], fallback: value.slice(fallbackStart, i).trim() || null };
}
/**
* Inline every `var()` in `value` with the referenced hook's declaration,
* recursively. Unknown names use their fallback (`var(--lwc-brandPrimary, …)`)
* or stay as written; a reference cycle stops at the repeated hook.
*
* With a `mode`, each hook contributes its `mode` declaration and the result's
* `light-dark()` calls are collapsed; without one, the full `light-dark()`
* declarations are kept.
*/
export function resolveValue(
tokens: TokenMap,
value: string,
mode?: Mode,
seen: ReadonlySet<string> = new Set(),
): string {
let out = '';
let pos = 0;
for (;;) {
const start = value.indexOf('var(', pos);
if (start === -1) {
out += value.slice(pos);
break;
}
out += value.slice(pos, start);
const call = parseVarCall(value, start);
Iif (!call) {
out += 'var(';
pos = start + 4;
continue;
}
pos = call.end;
const token = getToken(tokens, call.name);
if (token && !seen.has(call.name)) {
const declaration = mode ? modeDeclaration(token, mode) : cssDeclaration(token);
out += resolveValue(tokens, declaration, mode, new Set([...seen, call.name]));
} else if (!token && call.fallback) {
out += resolveValue(tokens, call.fallback, mode, seen);
} else {
out += value.slice(start, call.end);
}
}
return mode ? selectColorScheme(out, mode) : out;
}
/**
* The first SLDS hook a value references, e.g. `--slds-r-color-brand-40`.
* Skips non-SLDS names such as Lightning Blue's `var(--lwc-brandPrimary, …)`.
*/
export function firstReference(value: string): string | undefined {
return value.match(/var\(\s*(--slds-[\w-]+)/)?.[1];
}
/**
* The palette alias a hook's `mode` value ends at, following hook references:
* `slds-g-color-accent-2` → `{slds.r.color.brand.40}` → `{alias.palette.electricBlue.40}`.
*/
export function paletteAliasFor(
tokens: TokenMap,
token: FlatToken,
mode: Mode,
seen = new Set<string>(),
): string | undefined {
Iif (seen.has(token.name)) return undefined;
seen.add(token.name);
const reference = modeExtension(token, mode)?.reference ?? token.originalValue;
if (reference && PALETTE_ALIAS.test(reference)) return reference;
const hook = hookFromReference(reference) ?? firstReference(modeDeclaration(token, mode));
const next = hook ? getToken(tokens, hook) : undefined;
return next ? paletteAliasFor(tokens, next, mode, seen) : undefined;
}
const ROOT_FONT_SIZE = 16;
/** A `px` / `rem` length in pixels; undefined for anything else. */
export function toPx(value: string): number | undefined {
const match = value.trim().match(/^(-?\d*\.?\d+)(px|rem)$/);
if (!match) return undefined;
const n = Number(match[1]);
return match[2] === 'rem' ? n * ROOT_FONT_SIZE : n;
}
/** Trim float noise: 0.8125 * 16 → `13`, 1 / 3 → `0.333`. */
function formatNumber(n: number): string {
return String(Number(n.toFixed(3)));
}
export function formatPx(px: number): string {
return `${formatNumber(px)}px`;
}
export function formatRem(px: number): string {
return `${formatNumber(px / ROOT_FONT_SIZE)}rem`;
}
|