All files / packages/fds-uif/generator-static/src attribute-utils.ts

100% Statements 13/13
100% Branches 8/8
100% Functions 8/8
100% Lines 11/11

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                    6x                                           544x 11x   533x                   1032x 516x             554x                       61x               30x               11x             1673x    
/**
 * Attribute Utilities
 *
 * Pure, stateless functions for formatting HTML attributes, escaping values,
 * and converting between naming conventions (camelCase ↔ kebab-case).
 * No dependency on UIF types or generation context.
 */
 
export type AttributeValue = string | boolean | number;
 
export const VOID_ELEMENTS = new Set([
  'area',
  'base',
  'br',
  'col',
  'embed',
  'hr',
  'img',
  'input',
  'link',
  'meta',
  'param',
  'source',
  'track',
  'wbr',
]);
 
/**
 * Format a single HTML attribute as a key="value" string.
 * Boolean true renders the attribute name alone; false returns null.
 */
export function formatAttribute(key: string, value: AttributeValue): string | null {
  if (typeof value === 'boolean') {
    return value ? key : null;
  }
  return `${key}="${escapeAttributeValue(String(value))}"`;
}
 
/**
 * Format a bound attribute driven by an active state or modifier boolean.
 * ARIA attributes (aria-*) require an explicit string value ("true"/"false"),
 * while boolean HTML attributes (disabled, readonly, etc.) are presence-based.
 * Returns null when the state is inactive.
 */
export function formatStateAttribute(key: string, active: boolean): string | null {
  if (!active) return null;
  return key.startsWith('aria-') ? `${key}="true"` : key;
}
 
/**
 * Escape special characters in HTML attribute values.
 */
export function escapeAttributeValue(value: string): string {
  return value
    .replaceAll('&', '&')
    .replaceAll('"', '"')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;');
}
 
/**
 * Convert a camelCase or PascalCase name to kebab-case.
 * e.g., "iconName" → "icon-name", "ButtonIcon" → "button-icon"
 */
export function toKebabCase(name: string): string {
  return name.replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
}
 
/**
 * Convert a PascalCase component name to an LBC custom element tag.
 * e.g., "Icon" → "lightning-icon", "ButtonIcon" → "lightning-button-icon"
 */
export function toLbcTagName(componentName: string): string {
  return `lightning-${toKebabCase(componentName)}`;
}
 
/**
 * Convert a camelCase or PascalCase name to a human-readable label.
 * e.g., "fieldLevelHelp" → "Field Level Help"
 */
export function humanize(name: string): string {
  return name.replaceAll(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (s) => s.toUpperCase());
}
 
/**
 * Produce an indentation string of the given depth (2 spaces per level).
 */
export function indent(levels: number): string {
  return '  '.repeat(levels);
}