All files / packages/sds-metadata/src/utils value-resolver.ts

100% Statements 40/40
100% Branches 40/40
100% Functions 7/7
100% Lines 40/40

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              23x       16x       6x     2x                                 9x           14x 15x 1x   14x 12x   2x 1x     1x               13x 15x 1x   14x 5x   9x 2x   7x 3x   4x 2x     1x                             15x 15x   15x 17x 1x   16x 2x   16x 11x       15x 9x 9x 9x 6x     9x    
import valueParser from 'postcss-value-parser';
import type { StylingHookMetadata } from '../types.js';
 
type ParsedNode = ReturnType<typeof valueParser>['nodes'][number];
type FunctionNode = Extract<ParsedNode, { type: 'function' }>;
 
function isVarFunction(node: ParsedNode): node is FunctionNode {
  return node.type === 'function' && node.value.toLowerCase() === 'var';
}
 
function isLightDarkFunction(node: ParsedNode): node is FunctionNode {
  return node.type === 'function' && node.value.toLowerCase() === 'light-dark';
}
 
function isDivideOperator(node: ParsedNode): boolean {
  return node.type === 'div' && node.value === ',';
}
 
const COLOR_FUNCTIONS = new Set(
  [
    'rgb',
    'rgba',
    'hsl',
    'hsla',
    'lab',
    'lch',
    'oklch',
    'oklab',
    'color',
    'hwb',
    'device-cmyk',
  ]
);
 
function isColorFunction(node: ParsedNode): node is FunctionNode {
  return node.type === 'function' && COLOR_FUNCTIONS.has(node.value.toLowerCase());
}
 
 
/** First non-space child inside `var(...)` as token text (e.g. `--slds-r-foo`). */
function varInnerToken(varFn: FunctionNode): string | null {  
  for(const node of varFn.nodes){
    if(node.type === 'space'){
      continue;
    }
    if(node.type === 'word'){
      return node.value;
    }
    if(isDivideOperator(node)){
      return null;
    }
  }
  return null;
}
 
/**
 * Finds the first `light-dark(...)` in the value and returns a normalized light-side token:
 * `var(--x)` → inner token `--x`; hex / palette words → word value; `rgb()` / etc. → stringified function.
 */
export function parseLightDarkLightArg(lightDarkFn: FunctionNode): string | null {
  for(const child of lightDarkFn.nodes){
    if(child.type === 'space'){
      continue;
    }
    if(child.type === 'word'){
      return child.value;
    }
    if(isColorFunction(child)){
      return valueParser.stringify(child).trim();
    }
    if(isVarFunction(child)){
      return varInnerToken(child);
    }
    if(isDivideOperator(child)){
      return null;
    }
  }
  return null;
}
 
 
/**
 * v1: unwrap first `light-dark` to a light token, then resolve `var(--slds-r-*)` or bare `--slds-r-*`
 * via `reference[token].values[theme]`. Other values pass through (after light-dark unwrap when applicable).
 * On missing reference or missing theme value, returns the original input unchanged.
 */
export function resolveReferenceOnlyForPivotValue(
  original: string,
  theme: 'slds' | 'cosmos',
  reference: Record<string, StylingHookMetadata>
): string {
 
  const parsed = valueParser(original.trim());
  let result: string | null = null;
 
  for(const node of parsed.nodes){
    if(node.type === 'space'){
      continue;
    }
    if(isLightDarkFunction(node)){
      result = parseLightDarkLightArg(node);
    }
    if(isVarFunction(node)){
      result = varInnerToken(node);
    }
  }
 
  if(result && result.startsWith('--slds-r-')){
    const hook = reference?.[result];
    const resolved = hook?.values?.[theme];
    if (resolved && resolved.length > 0) {
      return resolved;
    }
  }
  return result ?? original;
}