@homebound/truss 2.29.12 → 2.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,10 @@ interface TrussMapping {
7
7
  typography?: string[];
8
8
  /** Token member name → CSS variable (from `config.tokens`), for `setVar` key resolution. */
9
9
  tokens?: Record<string, string>;
10
+ /** CSS variable → its `@property` block, for the tokens `config.tokens` registers with a `syntax`. */
11
+ properties?: Record<string, string>;
12
+ /** Keyframe name → its `@keyframes` block (from `config.keyframes`). */
13
+ keyframes?: Record<string, string>;
10
14
  abbreviations: Record<string, TrussMappingEntry>;
11
15
  }
12
16
  /**
@@ -267,6 +267,44 @@ function toVirtualCssSpecifier(source) {
267
267
  // src/plugin/transform-session.ts
268
268
  import { resolve as resolve2 } from "path";
269
269
 
270
+ // src/plugin/at-rule-refs.ts
271
+ var ANIMATION_VALUE_RE = /\banimation(?:-name)?\s*:\s*([^;}]+)/g;
272
+ function applyReferencedKeyframes(css, mapping) {
273
+ const configured = mapping.keyframes ?? {};
274
+ if (Object.keys(configured).length === 0) return;
275
+ const cssTexts = [...css.rules.map((rule) => rule.cssText), ...css.arbitraryCssBlocks.map((block) => block.cssText)];
276
+ const alreadyEmitted = new Set(css.keyframes.map((block) => block.name));
277
+ for (const name of referencedKeyframes(cssTexts, Object.keys(configured))) {
278
+ if (alreadyEmitted.has(name) || configured[name].length === 0) continue;
279
+ css.keyframes.push({ name, cssText: configured[name] });
280
+ }
281
+ }
282
+ function applyRegisteredProperties(css, mapping) {
283
+ const pending = new Map(Object.entries(mapping.properties ?? {}));
284
+ if (pending.size === 0) return;
285
+ for (let i = 0; i < css.properties.length; i++) {
286
+ const varName = css.properties[i].varName;
287
+ const cssText = pending.get(varName);
288
+ if (cssText === void 0) continue;
289
+ css.properties[i] = { varName, cssText };
290
+ pending.delete(varName);
291
+ }
292
+ for (const [varName, cssText] of pending) {
293
+ css.properties.push({ varName, cssText });
294
+ }
295
+ }
296
+ function referencedKeyframes(cssTexts, names) {
297
+ if (names.length === 0) return [];
298
+ const referenced = /* @__PURE__ */ new Set();
299
+ for (const cssText of cssTexts) {
300
+ for (const match of cssText.matchAll(ANIMATION_VALUE_RE)) {
301
+ if (match[1].includes("var(")) return names;
302
+ for (const token of match[1].trim().split(/[\s,]+/)) referenced.add(token);
303
+ }
304
+ }
305
+ return names.filter((name) => referenced.has(name));
306
+ }
307
+
270
308
  // src/plugin/resolve-chain.ts
271
309
  import * as t10 from "@babel/types";
272
310
 
@@ -333,11 +371,11 @@ function resetConditionContext(context) {
333
371
  // src/plugin/unknown-abbreviation.ts
334
372
  var UnknownAbbreviationError = class extends UnsupportedPatternError {
335
373
  constructor(abbreviation, candidates) {
336
- const suggestion = closestAbbreviation(abbreviation, candidates);
374
+ const suggestion = closestName(abbreviation, candidates);
337
375
  super(`Unknown abbreviation "${abbreviation}"${suggestion ? `. Did you mean "${suggestion}"?` : ""}`);
338
376
  }
339
377
  };
340
- function closestAbbreviation(abbreviation, candidates) {
378
+ function closestName(abbreviation, candidates) {
341
379
  let closest;
342
380
  let bestDistance = 3;
343
381
  for (const candidate of candidates) {
@@ -414,6 +452,7 @@ function hasCondition(condition) {
414
452
 
415
453
  // src/plugin/resolve-literals.ts
416
454
  import * as t3 from "@babel/types";
455
+ import { pascalCase } from "change-case";
417
456
 
418
457
  // src/css-custom-property.ts
419
458
  function maybeCssVar(value) {
@@ -428,6 +467,65 @@ function isCustomPropertyName(value) {
428
467
  return value.startsWith("--");
429
468
  }
430
469
 
470
+ // src/plugin/keyframe-names.ts
471
+ var ANIMATION_KEYWORDS = /* @__PURE__ */ new Set([
472
+ // CSS-wide
473
+ "inherit",
474
+ "initial",
475
+ "unset",
476
+ "revert",
477
+ "revert-layer",
478
+ // <single-animation-iteration-count>, <single-animation-name>, <single-animation-fill-mode>
479
+ "infinite",
480
+ "none",
481
+ "forwards",
482
+ "backwards",
483
+ "both",
484
+ // <single-animation-direction>
485
+ "normal",
486
+ "reverse",
487
+ "alternate",
488
+ "alternate-reverse",
489
+ // <single-animation-play-state>
490
+ "running",
491
+ "paused",
492
+ // <easing-function>
493
+ "linear",
494
+ "ease",
495
+ "ease-in",
496
+ "ease-out",
497
+ "ease-in-out",
498
+ "step-start",
499
+ "step-end",
500
+ // <single-animation-timeline>
501
+ "auto"
502
+ ]);
503
+ var NUMERIC_TOKEN_RE = /^[+-]?(\d+\.?\d*|\.\d+)(m?s)?$/;
504
+ function validateAnimationValue(props, value, mapping) {
505
+ const configured = mapping.keyframes;
506
+ if (!configured || Object.keys(configured).length === 0) return;
507
+ if (!props.some((prop) => ANIMATION_PROPERTIES.has(prop))) return;
508
+ if (value.includes("var(")) return;
509
+ for (const rawToken of value.trim().split(/[\s,]+/)) {
510
+ const token = rawToken.replace(/^["']|["']$/g, "");
511
+ if (token.length === 0) continue;
512
+ if (token.includes("(") || token.includes(")") || token.startsWith("--")) continue;
513
+ if (NUMERIC_TOKEN_RE.test(token)) continue;
514
+ if (ANIMATION_KEYWORDS.has(token.toLowerCase())) continue;
515
+ if (Object.hasOwn(configured, token)) continue;
516
+ throw new UnknownKeyframesError(token, Object.keys(configured));
517
+ }
518
+ }
519
+ var ANIMATION_PROPERTIES = /* @__PURE__ */ new Set(["animation", "animationName", "animation-name"]);
520
+ var UnknownKeyframesError = class extends UnsupportedPatternError {
521
+ constructor(name, candidates) {
522
+ const suggestion = closestName(name, candidates);
523
+ super(
524
+ `Unknown keyframes "${name}" - add it to config.keyframes${suggestion ? `. Did you mean "${suggestion}"?` : ""}`
525
+ );
526
+ }
527
+ };
528
+
431
529
  // src/spacing-css-var.ts
432
530
  var SPACING_CUSTOM_PROPERTY = "--t-spacing";
433
531
  function incrementCssValue(multiplier) {
@@ -460,6 +558,10 @@ function tryResolveValueLiteral(node, mapping) {
460
558
  if (mapping) {
461
559
  const token = tryResolveTokensMember(node, mapping);
462
560
  if (token !== null) return token;
561
+ const keyframes = tryResolveKeyframesMember(node, mapping);
562
+ if (keyframes !== null) return keyframes;
563
+ const template = tryResolveTemplateLiteral(node, mapping);
564
+ if (template !== null) return template;
463
565
  }
464
566
  if (t3.isStringLiteral(node)) {
465
567
  return node.value;
@@ -467,6 +569,19 @@ function tryResolveValueLiteral(node, mapping) {
467
569
  const numeric = tryNumericLiteral(node);
468
570
  return numeric === null ? null : String(numeric);
469
571
  }
572
+ function tryResolveTemplateLiteral(node, mapping) {
573
+ if (!t3.isTemplateLiteral(node)) return null;
574
+ let result = node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
575
+ for (let i = 0; i < node.expressions.length; i++) {
576
+ const expression = node.expressions[i];
577
+ if (!t3.isExpression(expression)) return null;
578
+ const resolved = tryResolveValueLiteral(expression, mapping);
579
+ if (resolved === null) return null;
580
+ const quasi = node.quasis[i + 1];
581
+ result += maybeCssVar(resolved) + (quasi.value.cooked ?? quasi.value.raw);
582
+ }
583
+ return result;
584
+ }
470
585
  function tryNumericLiteral(node) {
471
586
  if (t3.isNumericLiteral(node)) {
472
587
  return node.value;
@@ -489,6 +604,23 @@ function tryResolveTokensMember(node, mapping) {
489
604
  }
490
605
  return tokenMap[memberName];
491
606
  }
607
+ function tryResolveKeyframesMember(node, mapping) {
608
+ if (!t3.isMemberExpression(node) || !t3.isIdentifier(node.object, { name: "Keyframes" })) return null;
609
+ const memberName = memberPropertyName(node);
610
+ if (memberName === null) return null;
611
+ const keyframesMap = mapping.keyframes;
612
+ if (!keyframesMap) {
613
+ throw new UnsupportedPatternError(`Keyframes.* requires config.keyframes`);
614
+ }
615
+ const name = Object.keys(keyframesMap).find((configured) => pascalCase(configured) === memberName);
616
+ if (name === void 0) {
617
+ throw new UnknownKeyframesError(
618
+ memberName,
619
+ Object.keys(keyframesMap).map((configured) => pascalCase(configured))
620
+ );
621
+ }
622
+ return name;
623
+ }
492
624
  function singleArg(node, label) {
493
625
  if (node.args.length !== 1) {
494
626
  throw new UnsupportedPatternError(`${label}() expects exactly 1 argument, got ${node.args.length}`);
@@ -540,7 +672,7 @@ function requireValueLiteral(node, errorMessage) {
540
672
 
541
673
  // src/plugin/resolve-setvar.ts
542
674
  import * as t6 from "@babel/types";
543
- import { pascalCase } from "change-case";
675
+ import { pascalCase as pascalCase2 } from "change-case";
544
676
 
545
677
  // src/plugin/container-query.ts
546
678
  import * as t4 from "@babel/types";
@@ -592,6 +724,8 @@ import * as t5 from "@babel/types";
592
724
 
593
725
  // src/plugin/css-property-abbreviations.ts
594
726
  var cssPropertyAbbreviations = {
727
+ // Accent color
728
+ accentColor: "acc",
595
729
  // Alignment
596
730
  alignContent: "ac",
597
731
  alignItems: "ai",
@@ -1134,7 +1268,7 @@ function computeStaticBaseName(seg, cssProp, cssValue, isMultiProp, mapping) {
1134
1268
  return canonical ?? `${getPropertyAbbreviation(cssProp)}_${classNameFragmentForResolvedValue(cssValue)}`;
1135
1269
  }
1136
1270
  if (seg.argResolved !== void 0) {
1137
- return `${seg.abbr}_${classNameFragmentForResolvedValue(seg.argResolved)}`;
1271
+ return `${getPropertyAbbreviation(seg.abbr)}_${classNameFragmentForResolvedValue(seg.argResolved)}`;
1138
1272
  }
1139
1273
  return seg.abbr;
1140
1274
  }
@@ -1161,9 +1295,6 @@ function whenPrefix(whenPseudo) {
1161
1295
  const markerPart = whenPseudo.markerNode ? `${whenPseudo.markerNode.name}_` : "";
1162
1296
  return `wh_${rel}_${pseudoPrefix}_${markerPart}`;
1163
1297
  }
1164
- function camelToKebab(s) {
1165
- return s.replace(/^(Webkit|Moz|Ms|O)/, (m) => `-${m.toLowerCase()}`).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
1166
- }
1167
1298
  function sanitizeClassNameToken(value) {
1168
1299
  return value.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1169
1300
  }
@@ -1284,7 +1415,7 @@ function expandSetVarValueToLeaves(valueNode, mapping, baseContext) {
1284
1415
  }
1285
1416
  function setVarMediaLeaves(mediaObject, mapping, baseContext) {
1286
1417
  return plainObjectEntries(mediaObject, "setVar().media").map(({ key: breakpointName, value }) => {
1287
- const mediaQuery = breakpointMediaQuery(mapping, `if${pascalCase(breakpointName)}`);
1418
+ const mediaQuery = breakpointMediaQuery(mapping, `if${pascalCase2(breakpointName)}`);
1288
1419
  if (mediaQuery === null) {
1289
1420
  throw new UnsupportedPatternError(
1290
1421
  `Unknown breakpoint "${breakpointName}" in setVar().media - use a Breakpoint name from truss-config`
@@ -1445,6 +1576,7 @@ function resolveDelegateCall(abbr, entry, node, mapping, context) {
1445
1576
  }
1446
1577
  function resolveLiteralOrVariableSegment(params) {
1447
1578
  const { abbr, props, incremented, appendPx = false, extraDefs, argAst, literalValue, mapping, context } = params;
1579
+ if (literalValue !== null) validateAnimationValue(props, literalValue, mapping);
1448
1580
  if (literalValue !== null && !isCustomPropertyLiteral(argAst, mapping)) {
1449
1581
  const defs = Object.fromEntries(props.map((prop) => [prop, literalValue]));
1450
1582
  return staticSegment(abbr, { ...defs, ...extraDefs }, context, literalValue);
@@ -2529,16 +2661,24 @@ function sortRulesByPriority(rules) {
2529
2661
  return decorated.map((d) => ({ rule: d.rule, priority: d.priority }));
2530
2662
  }
2531
2663
 
2664
+ // src/utils.ts
2665
+ function camelToKebab(s) {
2666
+ return s.replace(/^(Webkit|Moz|Ms|O)/, (m) => `-${m.toLowerCase()}`).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
2667
+ }
2668
+
2532
2669
  // src/plugin/truss-css.ts
2533
2670
  var RULE_ANNOTATION_RE = /^\/\* @truss p:([\d.]+) c:(\S+) \*\/$/;
2534
2671
  var PROPERTY_ANNOTATION_RE = /^\/\* @truss @property \*\/$/;
2672
+ var KEYFRAMES_ANNOTATION_RE = /^\/\* @truss @keyframes \*\/$/;
2535
2673
  var ARBITRARY_START_RE = /^\/\* @truss arbitrary:start \*\/$/;
2536
2674
  var ARBITRARY_END_RE = /^\/\* @truss arbitrary:end \*\/$/;
2537
2675
  var PROPERTY_VAR_RE = /^@property\s+(--\S+)/;
2676
+ var KEYFRAMES_NAME_RE = /^@keyframes\s+([^\s{]+)/;
2538
2677
  function parseTrussCss(cssText) {
2539
2678
  const lines = cssText.split("\n");
2540
2679
  const rules = [];
2541
2680
  const properties = [];
2681
+ const keyframes = [];
2542
2682
  const arbitraryCssBlocks = [];
2543
2683
  let i = 0;
2544
2684
  function takeAnnotatedLine() {
@@ -2566,6 +2706,15 @@ function parseTrussCss(cssText) {
2566
2706
  i++;
2567
2707
  continue;
2568
2708
  }
2709
+ if (KEYFRAMES_ANNOTATION_RE.test(line)) {
2710
+ const keyframesLine = takeAnnotatedLine();
2711
+ const nameMatch = keyframesLine === null ? null : KEYFRAMES_NAME_RE.exec(keyframesLine);
2712
+ if (keyframesLine !== null && nameMatch) {
2713
+ keyframes.push({ cssText: keyframesLine, name: nameMatch[1] });
2714
+ }
2715
+ i++;
2716
+ continue;
2717
+ }
2569
2718
  if (ARBITRARY_START_RE.test(line)) {
2570
2719
  i++;
2571
2720
  const blockLines = [];
@@ -2584,7 +2733,7 @@ function parseTrussCss(cssText) {
2584
2733
  }
2585
2734
  i++;
2586
2735
  }
2587
- return { rules, properties, arbitraryCssBlocks };
2736
+ return { rules, properties, keyframes, arbitraryCssBlocks };
2588
2737
  }
2589
2738
  function serializeTrussCss(css, annotate = true) {
2590
2739
  const lines = [];
@@ -2596,6 +2745,10 @@ function serializeTrussCss(css, annotate = true) {
2596
2745
  if (annotate) lines.push(`/* @truss @property */`);
2597
2746
  lines.push(prop.cssText);
2598
2747
  }
2748
+ for (const block of css.keyframes) {
2749
+ if (annotate) lines.push(`/* @truss @keyframes */`);
2750
+ lines.push(block.cssText);
2751
+ }
2599
2752
  for (const block of css.arbitraryCssBlocks) {
2600
2753
  lines.push(annotate ? annotateArbitraryCssBlock(block.cssText) : block.cssText.trim());
2601
2754
  }
@@ -2677,6 +2830,7 @@ function generateCssData(rules) {
2677
2830
  cssText: formatRule(entry.rule)
2678
2831
  })),
2679
2832
  properties: [],
2833
+ keyframes: [],
2680
2834
  arbitraryCssBlocks: []
2681
2835
  };
2682
2836
  for (const { rule } of sorted) {
@@ -2965,6 +3119,7 @@ function createTestCssPayload(css) {
2965
3119
  });
2966
3120
  }
2967
3121
  if (css.properties.length > 0) payload.properties = css.properties;
3122
+ if (css.keyframes.length > 0) payload.keyframes = css.keyframes;
2968
3123
  const arbitraryRules = css.arbitraryCssBlocks.flatMap((block) => splitArbitraryCss(block.cssText));
2969
3124
  if (arbitraryRules.length > 0) payload.arbitraryRules = arbitraryRules;
2970
3125
  return payload;
@@ -3461,6 +3616,7 @@ function transformTruss(code, filename, mapping, options = {}) {
3461
3616
  const chains = sites.map((s) => s.resolvedChain);
3462
3617
  const { rules, needsMaybeInc, needsMaybeCssVar } = collectAtomicRules(chains, mapping);
3463
3618
  const cssData = generateCssData(rules);
3619
+ applyReferencedKeyframes(cssData, mapping);
3464
3620
  const cssText = serializeTrussCss(cssData);
3465
3621
  const runtime = createRuntimeHelpers(ast, usedTopLevelNames);
3466
3622
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
@@ -3637,6 +3793,8 @@ function mergeTrussCssData(sources) {
3637
3793
  const allRules = [];
3638
3794
  const seenProperties = /* @__PURE__ */ new Set();
3639
3795
  const allProperties = [];
3796
+ const seenKeyframes = /* @__PURE__ */ new Set();
3797
+ const allKeyframes = [];
3640
3798
  const allArbitraryCssBlocks = [];
3641
3799
  for (const source of sources) {
3642
3800
  for (const rule of source.rules) {
@@ -3651,6 +3809,12 @@ function mergeTrussCssData(sources) {
3651
3809
  allProperties.push(prop);
3652
3810
  }
3653
3811
  }
3812
+ for (const block of source.keyframes) {
3813
+ if (!seenKeyframes.has(block.name)) {
3814
+ seenKeyframes.add(block.name);
3815
+ allKeyframes.push(block);
3816
+ }
3817
+ }
3654
3818
  allArbitraryCssBlocks.push(...source.arbitraryCssBlocks);
3655
3819
  }
3656
3820
  const decorated = allRules.map((rule) => {
@@ -3660,6 +3824,7 @@ function mergeTrussCssData(sources) {
3660
3824
  return {
3661
3825
  rules: decorated.map((entry) => entry.rule),
3662
3826
  properties: allProperties,
3827
+ keyframes: allKeyframes,
3663
3828
  arbitraryCssBlocks: allArbitraryCssBlocks
3664
3829
  };
3665
3830
  }
@@ -3725,7 +3890,10 @@ function createTrussTransformSession(options) {
3725
3890
  const allArbitrary = Array.from(arbitraryCssRegistry.entries()).sort((a, b) => compareClassNames(a[0], b[0])).map((entry) => entry[1]).join("\n\n");
3726
3891
  if (allArbitrary.length > 0) appCss.arbitraryCssBlocks.push({ cssText: allArbitrary });
3727
3892
  const libs = loadLibraries();
3728
- const body = serializeTrussCss(libs.length === 0 ? appCss : mergeTrussCssData([...libs, appCss]), annotate);
3893
+ const merged = libs.length === 0 ? appCss : mergeTrussCssData([...libs, appCss]);
3894
+ applyReferencedKeyframes(merged, mapping2);
3895
+ applyRegisteredProperties(merged, mapping2);
3896
+ const body = serializeTrussCss(merged, annotate);
3729
3897
  if (body.length === 0) return "";
3730
3898
  return `${rootSpacingPreludeCss(mapping2.increment)}
3731
3899
  ${body}`;
@@ -3734,7 +3902,9 @@ ${body}`;
3734
3902
  return cssRegistry.size > 0 || arbitraryCssRegistry.size > 0 || libraryPaths.length > 0;
3735
3903
  }
3736
3904
  function collectTestCss() {
3737
- return createTestCssPayload(mergeTrussCssData(loadLibraries()));
3905
+ const css = mergeTrussCssData(loadLibraries());
3906
+ applyRegisteredProperties(css, ensureMapping());
3907
+ return createTestCssPayload(css);
3738
3908
  }
3739
3909
  function getArbitraryCss(sourcePath) {
3740
3910
  return arbitraryCssRegistry.get(resolve2(sourcePath).replace(/\\/g, "/")) ?? "";
@@ -3947,8 +4117,17 @@ __injectTrussCSS(${JSON.stringify(payload)});
3947
4117
  if (id.startsWith(VIRTUAL_TEST_CSS_PREFIX)) {
3948
4118
  const sourcePath2 = canonicalSourcePath(id.slice(VIRTUAL_TEST_CSS_PREFIX.length));
3949
4119
  session.updateArbitraryCssRegistry(sourcePath2, readFileSync4(sourcePath2, "utf8"), diagnostics(this));
4120
+ const arbitraryRules = splitArbitraryCss(session.getArbitraryCss(sourcePath2));
4121
+ const atRules = {
4122
+ rules: [],
4123
+ properties: [],
4124
+ keyframes: [],
4125
+ arbitraryCssBlocks: arbitraryRules.map((cssText) => ({ cssText }))
4126
+ };
4127
+ applyReferencedKeyframes(atRules, session.ensureMapping());
3950
4128
  const payload = {
3951
- arbitraryRules: splitArbitraryCss(session.getArbitraryCss(sourcePath2)),
4129
+ arbitraryRules,
4130
+ ...atRules.keyframes.length > 0 ? { keyframes: atRules.keyframes } : {},
3952
4131
  source: sourcePath2
3953
4132
  };
3954
4133
  return `