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                                155x 6x   152x 66x   86x                 60x   60x 11x 32x 27x           49x 49x                             155x 155x   155x 70x                             94x     94x 94x 5x 92x 31x       94x     94x 12x             94x     94x 94x 7x 32x 22x               94x    
/**
 * 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 }];
}