All files / packages/fds-uif/generator-base/src/analyzer classNames.ts

94.59% Statements 35/37
91.66% Branches 22/24
100% Functions 8/8
96.96% Lines 32/33

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                                169x 6x   166x 74x   92x                 61x   61x 11x 32x 27x           50x 50x                             169x 169x   169x 78x                             107x     107x 107x 5x 105x 38x       107x     107x 12x             107x     107x 107x 14x 46x 23x               107x    
/**
 * Class Name Rules
 *
 * Builds conditional class name rules from UIF modifiers, states, and variants.
 */
 
import type { ResolvedUIF, ResolvedModifier, ResolvedStructure } from '@fds-uif/schema';
import { getVariants } from '@fds-uif/core/browser';
 
import type { ClassNameRule } from '../types/index.js';
import { sanitizeIdentifier } from '../types/index.js';
 
/**
 * Convert a static class attribute value (string or string[]) to a flat array of class tokens.
 */
function parseStaticClass(staticClass: unknown): string[] {
  if (Array.isArray(staticClass)) {
    return staticClass.flatMap((c) => String(c).split(' ').filter(Boolean));
  }
  if (typeof staticClass === 'string') {
    return staticClass.split(' ').filter(Boolean);
  }
  return [];
}
 
/**
 * Build conditional class entries from a single modifier.
 * Grouped modifiers (with options) produce one equality condition per option;
 * boolean modifiers produce a single presence condition.
 */
function modifierToConditionals(modifier: ResolvedModifier): Array<{ classes: string[]; condition: string }> {
  Iif (modifier.attribute !== 'class') return [];
 
  if (modifier.options && modifier.options.length > 0) {
    return modifier.options
      .filter((o) => o.value)
      .map((o) => ({
        classes: [o.value],
        condition: `${sanitizeIdentifier(modifier.name)} === '${o.propValue}'`,
      }));
  }
 
  Eif (modifier.value) {
    return [{ classes: [modifier.value], condition: sanitizeIdentifier(modifier.name) }];
  }
 
  return [];
}
 
/**
 * Build class name rules for a single structure node from its own static class
 * and modifiers. Used by `analyzeStructure` to attach per-node class rules to
 * non-root elements that declare modifiers (e.g. a child `svg` with size options).
 *
 * Boolean modifiers produce a boolean condition; grouped modifiers (with `options`)
 * produce variant-style equality conditions so they resolve against `state.variants`.
 */
export function buildNodeClassNameRules(structure: ResolvedStructure): ClassNameRule[] {
  const baseClasses = parseStaticClass(structure.attributes?.static?.class);
  const conditional = (structure.modifiers ?? []).flatMap(modifierToConditionals);
 
  if (baseClasses.length === 0 && conditional.length === 0) return [];
  return [{ base: baseClasses, conditional }];
}
 
/**
 * Build class name rules for the ROOT element of a component from its
 * root-level modifiers, state classes, and root-level variants.
 *
 * Child-element modifiers are intentionally excluded here — they are handled
 * by `buildNodeClassNameRules` attached to each child `StructureMetadata`.
 */
export function buildClassNameRules(
  uif: ResolvedUIF,
  modifiers: ResolvedModifier[],
  stateClasses: Array<{ state: string; class: string }>,
): ClassNameRule[] {
  const baseClasses: string[] = [];
 
  // Extract base class from structure.attributes.static.class (string or string[])
  const staticClass = uif.structure.attributes?.static?.class;
  if (Array.isArray(staticClass)) {
    baseClasses.push(...staticClass.flatMap((c) => String(c).split(' ').filter(Boolean)));
  } else if (typeof staticClass === 'string') {
    baseClasses.push(...staticClass.split(' ').filter(Boolean));
  }
 
  // Build conditional rules
  const conditional: Array<{ classes: string[]; condition: string }> = [];
 
  // stateClasses → conditional on the active state
  for (const sc of stateClasses) {
    conditional.push({
      classes: [sc.class],
      condition: sanitizeIdentifier(sc.state),
    });
  }
 
  // Root-level modifiers → conditional classes (boolean and grouped)
  conditional.push(...modifiers.flatMap(modifierToConditionals));
 
  // Root-level variants → conditional classes (switch-style, value-based)
  const variants = getVariants(uif);
  for (const variant of variants) {
    for (const option of variant.options) {
      if (option.class) {
        conditional.push({
          classes: [option.class],
          condition: `${sanitizeIdentifier(variant.name)} === '${option.value}'`,
        });
      }
    }
  }
 
  return [{ base: baseClasses, conditional }];
}