@homebound/truss 2.29.13 → 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";
@@ -1163,9 +1295,6 @@ function whenPrefix(whenPseudo) {
1163
1295
  const markerPart = whenPseudo.markerNode ? `${whenPseudo.markerNode.name}_` : "";
1164
1296
  return `wh_${rel}_${pseudoPrefix}_${markerPart}`;
1165
1297
  }
1166
- function camelToKebab(s) {
1167
- return s.replace(/^(Webkit|Moz|Ms|O)/, (m) => `-${m.toLowerCase()}`).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
1168
- }
1169
1298
  function sanitizeClassNameToken(value) {
1170
1299
  return value.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1171
1300
  }
@@ -1286,7 +1415,7 @@ function expandSetVarValueToLeaves(valueNode, mapping, baseContext) {
1286
1415
  }
1287
1416
  function setVarMediaLeaves(mediaObject, mapping, baseContext) {
1288
1417
  return plainObjectEntries(mediaObject, "setVar().media").map(({ key: breakpointName, value }) => {
1289
- const mediaQuery = breakpointMediaQuery(mapping, `if${pascalCase(breakpointName)}`);
1418
+ const mediaQuery = breakpointMediaQuery(mapping, `if${pascalCase2(breakpointName)}`);
1290
1419
  if (mediaQuery === null) {
1291
1420
  throw new UnsupportedPatternError(
1292
1421
  `Unknown breakpoint "${breakpointName}" in setVar().media - use a Breakpoint name from truss-config`
@@ -1447,6 +1576,7 @@ function resolveDelegateCall(abbr, entry, node, mapping, context) {
1447
1576
  }
1448
1577
  function resolveLiteralOrVariableSegment(params) {
1449
1578
  const { abbr, props, incremented, appendPx = false, extraDefs, argAst, literalValue, mapping, context } = params;
1579
+ if (literalValue !== null) validateAnimationValue(props, literalValue, mapping);
1450
1580
  if (literalValue !== null && !isCustomPropertyLiteral(argAst, mapping)) {
1451
1581
  const defs = Object.fromEntries(props.map((prop) => [prop, literalValue]));
1452
1582
  return staticSegment(abbr, { ...defs, ...extraDefs }, context, literalValue);
@@ -2531,16 +2661,24 @@ function sortRulesByPriority(rules) {
2531
2661
  return decorated.map((d) => ({ rule: d.rule, priority: d.priority }));
2532
2662
  }
2533
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
+
2534
2669
  // src/plugin/truss-css.ts
2535
2670
  var RULE_ANNOTATION_RE = /^\/\* @truss p:([\d.]+) c:(\S+) \*\/$/;
2536
2671
  var PROPERTY_ANNOTATION_RE = /^\/\* @truss @property \*\/$/;
2672
+ var KEYFRAMES_ANNOTATION_RE = /^\/\* @truss @keyframes \*\/$/;
2537
2673
  var ARBITRARY_START_RE = /^\/\* @truss arbitrary:start \*\/$/;
2538
2674
  var ARBITRARY_END_RE = /^\/\* @truss arbitrary:end \*\/$/;
2539
2675
  var PROPERTY_VAR_RE = /^@property\s+(--\S+)/;
2676
+ var KEYFRAMES_NAME_RE = /^@keyframes\s+([^\s{]+)/;
2540
2677
  function parseTrussCss(cssText) {
2541
2678
  const lines = cssText.split("\n");
2542
2679
  const rules = [];
2543
2680
  const properties = [];
2681
+ const keyframes = [];
2544
2682
  const arbitraryCssBlocks = [];
2545
2683
  let i = 0;
2546
2684
  function takeAnnotatedLine() {
@@ -2568,6 +2706,15 @@ function parseTrussCss(cssText) {
2568
2706
  i++;
2569
2707
  continue;
2570
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
+ }
2571
2718
  if (ARBITRARY_START_RE.test(line)) {
2572
2719
  i++;
2573
2720
  const blockLines = [];
@@ -2586,7 +2733,7 @@ function parseTrussCss(cssText) {
2586
2733
  }
2587
2734
  i++;
2588
2735
  }
2589
- return { rules, properties, arbitraryCssBlocks };
2736
+ return { rules, properties, keyframes, arbitraryCssBlocks };
2590
2737
  }
2591
2738
  function serializeTrussCss(css, annotate = true) {
2592
2739
  const lines = [];
@@ -2598,6 +2745,10 @@ function serializeTrussCss(css, annotate = true) {
2598
2745
  if (annotate) lines.push(`/* @truss @property */`);
2599
2746
  lines.push(prop.cssText);
2600
2747
  }
2748
+ for (const block of css.keyframes) {
2749
+ if (annotate) lines.push(`/* @truss @keyframes */`);
2750
+ lines.push(block.cssText);
2751
+ }
2601
2752
  for (const block of css.arbitraryCssBlocks) {
2602
2753
  lines.push(annotate ? annotateArbitraryCssBlock(block.cssText) : block.cssText.trim());
2603
2754
  }
@@ -2679,6 +2830,7 @@ function generateCssData(rules) {
2679
2830
  cssText: formatRule(entry.rule)
2680
2831
  })),
2681
2832
  properties: [],
2833
+ keyframes: [],
2682
2834
  arbitraryCssBlocks: []
2683
2835
  };
2684
2836
  for (const { rule } of sorted) {
@@ -2967,6 +3119,7 @@ function createTestCssPayload(css) {
2967
3119
  });
2968
3120
  }
2969
3121
  if (css.properties.length > 0) payload.properties = css.properties;
3122
+ if (css.keyframes.length > 0) payload.keyframes = css.keyframes;
2970
3123
  const arbitraryRules = css.arbitraryCssBlocks.flatMap((block) => splitArbitraryCss(block.cssText));
2971
3124
  if (arbitraryRules.length > 0) payload.arbitraryRules = arbitraryRules;
2972
3125
  return payload;
@@ -3463,6 +3616,7 @@ function transformTruss(code, filename, mapping, options = {}) {
3463
3616
  const chains = sites.map((s) => s.resolvedChain);
3464
3617
  const { rules, needsMaybeInc, needsMaybeCssVar } = collectAtomicRules(chains, mapping);
3465
3618
  const cssData = generateCssData(rules);
3619
+ applyReferencedKeyframes(cssData, mapping);
3466
3620
  const cssText = serializeTrussCss(cssData);
3467
3621
  const runtime = createRuntimeHelpers(ast, usedTopLevelNames);
3468
3622
  const maybeIncHelperName = needsMaybeInc ? reservePreferredName(usedTopLevelNames, "__maybeInc") : null;
@@ -3639,6 +3793,8 @@ function mergeTrussCssData(sources) {
3639
3793
  const allRules = [];
3640
3794
  const seenProperties = /* @__PURE__ */ new Set();
3641
3795
  const allProperties = [];
3796
+ const seenKeyframes = /* @__PURE__ */ new Set();
3797
+ const allKeyframes = [];
3642
3798
  const allArbitraryCssBlocks = [];
3643
3799
  for (const source of sources) {
3644
3800
  for (const rule of source.rules) {
@@ -3653,6 +3809,12 @@ function mergeTrussCssData(sources) {
3653
3809
  allProperties.push(prop);
3654
3810
  }
3655
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
+ }
3656
3818
  allArbitraryCssBlocks.push(...source.arbitraryCssBlocks);
3657
3819
  }
3658
3820
  const decorated = allRules.map((rule) => {
@@ -3662,6 +3824,7 @@ function mergeTrussCssData(sources) {
3662
3824
  return {
3663
3825
  rules: decorated.map((entry) => entry.rule),
3664
3826
  properties: allProperties,
3827
+ keyframes: allKeyframes,
3665
3828
  arbitraryCssBlocks: allArbitraryCssBlocks
3666
3829
  };
3667
3830
  }
@@ -3727,7 +3890,10 @@ function createTrussTransformSession(options) {
3727
3890
  const allArbitrary = Array.from(arbitraryCssRegistry.entries()).sort((a, b) => compareClassNames(a[0], b[0])).map((entry) => entry[1]).join("\n\n");
3728
3891
  if (allArbitrary.length > 0) appCss.arbitraryCssBlocks.push({ cssText: allArbitrary });
3729
3892
  const libs = loadLibraries();
3730
- 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);
3731
3897
  if (body.length === 0) return "";
3732
3898
  return `${rootSpacingPreludeCss(mapping2.increment)}
3733
3899
  ${body}`;
@@ -3736,7 +3902,9 @@ ${body}`;
3736
3902
  return cssRegistry.size > 0 || arbitraryCssRegistry.size > 0 || libraryPaths.length > 0;
3737
3903
  }
3738
3904
  function collectTestCss() {
3739
- return createTestCssPayload(mergeTrussCssData(loadLibraries()));
3905
+ const css = mergeTrussCssData(loadLibraries());
3906
+ applyRegisteredProperties(css, ensureMapping());
3907
+ return createTestCssPayload(css);
3740
3908
  }
3741
3909
  function getArbitraryCss(sourcePath) {
3742
3910
  return arbitraryCssRegistry.get(resolve2(sourcePath).replace(/\\/g, "/")) ?? "";
@@ -3949,8 +4117,17 @@ __injectTrussCSS(${JSON.stringify(payload)});
3949
4117
  if (id.startsWith(VIRTUAL_TEST_CSS_PREFIX)) {
3950
4118
  const sourcePath2 = canonicalSourcePath(id.slice(VIRTUAL_TEST_CSS_PREFIX.length));
3951
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());
3952
4128
  const payload = {
3953
- arbitraryRules: splitArbitraryCss(session.getArbitraryCss(sourcePath2)),
4129
+ arbitraryRules,
4130
+ ...atRules.keyframes.length > 0 ? { keyframes: atRules.keyframes } : {},
3954
4131
  source: sourcePath2
3955
4132
  };
3956
4133
  return `