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 | 503x 11x 498x 290x 208x 87x 87x 12x 46x 41x 75x 75x 43x 43x 124x 92x 43x 385x 257x 385x 118x 118x 118x 21x 118x | /**
* Class Name Rules
*
* Builds conditional class name rules from UIF modifiers, states, and variants.
*/
import type { ResolvedUIF, ResolvedModifier, ResolvedStructure, ResolvedVariant } from '@fds-uif/schema';
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 [];
}
type Conditional = { classes: string[]; condition: string };
/**
* 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): Conditional[] {
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 conditional class entries from a single variant. One option = one equality
* conditional. Empty `class` (the "no-class default" pattern) yields no entry so the
* default doesn't accidentally clear classes the resolver would otherwise apply.
*/
function variantToConditionals(variant: ResolvedVariant): Conditional[] {
const out: Conditional[] = [];
for (const option of variant.options) {
if (!option.class) continue;
out.push({
classes: [option.class],
condition: `${sanitizeIdentifier(variant.name)} === '${option.value}'`,
});
}
return out;
}
/**
* Build a `ClassNameRule` from the constituent parts. Returns an empty list when
* there's nothing to declare so callers can `ClassNameRule[]` cleanly.
*/
function ruleFrom(baseClasses: string[], conditional: Conditional[]): ClassNameRule[] {
if (baseClasses.length === 0 && conditional.length === 0) return [];
return [{ base: baseClasses, conditional }];
}
/**
* Build class name rules for a single structure node from its own static class,
* modifiers, AND variants. Used by `analyzeStructure` to attach per-node class
* rules to non-root elements that declare local modifiers/variants.
*
* Variants here are read off the NODE (`structure.variants`), not the UIF root — a
* variant declared on a child node only paints that node, not the component root.
* This was the source of the "child variant class hoists to root" bug: previously
* `buildClassNameRules` pulled every variant across the tree via getVariants(uif)
* and slapped them onto the root's rule list. Now variants live on their own node.
*/
export function buildNodeClassNameRules(structure: ResolvedStructure): ClassNameRule[] {
return ruleFrom(parseStaticClass(structure.attributes?.static?.class), [
...(structure.modifiers ?? []).flatMap(modifierToConditionals),
...(structure.variants ?? []).flatMap(variantToConditionals),
]);
}
/**
* Build class name rules for the ROOT element of a component from its
* root-level modifiers, state classes, and root-level variants.
*
* Reads variants from TWO root sources: `uif.variants` (the system layer's
* top-level variants block) and `uif.structure.variants` (variants declared
* on the root structure node). Does NOT walk descendants — pulling variants
* recursively would re-introduce the class-hoisting bug where child-node
* variants leak onto the root. Child-node variants are handled by
* buildNodeClassNameRules attached to each child's metadata.
*/
export function buildClassNameRules(
uif: ResolvedUIF,
modifiers: ResolvedModifier[],
stateClasses: Array<{ state: string; class: string }>,
): ClassNameRule[] {
const baseClasses = parseStaticClass(uif.structure.attributes?.static?.class);
const rootVariants = [...(uif.variants ?? []), ...(uif.structure.variants ?? [])];
const conditional: Conditional[] = [
...stateClasses.map((sc) => ({
classes: [sc.class],
condition: sanitizeIdentifier(sc.state),
})),
...modifiers.flatMap(modifierToConditionals),
...rootVariants.flatMap(variantToConditionals),
];
// The root always emits a ClassNameRule (even when nothing's declared) so the
// resolver can attach extra root classes from the generation context.
return [{ base: baseClasses, conditional }];
}
|