@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.
package/build/index.d.ts CHANGED
@@ -11,8 +11,58 @@ type FontConfig = Record<string, string | Properties>;
11
11
  /**
12
12
  * Maps a design token name (enum member / key) to a CSS custom property name.
13
13
  * Values must be valid custom property identifiers (`--…`).
14
+ *
15
+ * The object form additionally registers the property with `@property`, i.e. so the browser
16
+ * types the value and can animate it. See `TokenDefinition`.
14
17
  */
15
- type TokenRegistry = Record<string, `--${string}`>;
18
+ type TokenRegistry = Record<string, `--${string}` | TokenDefinition>;
19
+ /**
20
+ * A token that is also registered with `@property`.
21
+ *
22
+ * Naming a token and registering it are two different things. The string form of `tokens` only
23
+ * gives the variable a TypeScript name and emits no CSS. Registering it tells the browser about
24
+ * the variable: to parse and type-check its value, to fall back to `initialValue` instead of
25
+ * inheriting an unparsed token stream, and — the reason most design systems want it — to
26
+ * *interpolate* it, since an unregistered custom property is an opaque token stream that no
27
+ * transition or keyframe can animate.
28
+ *
29
+ * I.e. `{ var: "--angle", syntax: "<angle>", inherits: false, initialValue: "0deg" }` emits
30
+ * `@property --angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }`.
31
+ */
32
+ interface TokenDefinition {
33
+ /** The CSS custom property name, i.e. `--angle`. */
34
+ var: `--${string}`;
35
+ /** The `@property` syntax descriptor, i.e. `"<angle>"`, or `"*"` to register without typing. */
36
+ syntax: string;
37
+ /** The `@property` inherits descriptor. Defaults to `false`. */
38
+ inherits?: boolean;
39
+ /** The `@property` initial-value descriptor, i.e. `"0deg"`. Required unless `syntax` is `"*"`. */
40
+ initialValue?: string;
41
+ }
42
+ /**
43
+ * A map from `@keyframes` name to its animation timeline.
44
+ *
45
+ * The object form is keyframe selector (`from`, `to`, `50%`, `0%, 100%`) to declarations, which
46
+ * csstype validates like any other declaration. The string form is a raw body, for a timeline the
47
+ * typed form cannot express. Either way Truss owns the name, so it can check the animations that
48
+ * use it and write the block only when one still does.
49
+ *
50
+ * `null` declares a name that some other stylesheet defines — a global CSS file, a third-party
51
+ * package. Truss accepts the name and writes nothing. Without it, adopting `keyframes` at all would
52
+ * make every animation Truss did not declare fail the build.
53
+ *
54
+ * I.e. `{ spin: { to: { transform: "rotate(360deg)" } }, aiStarLoader: null }`.
55
+ */
56
+ type KeyframesConfig = Record<string, Record<string, KeyframeDeclarations> | string | null>;
57
+ /**
58
+ * The declarations in one keyframe selector.
59
+ *
60
+ * Custom properties are allowed alongside real CSS properties, because animating a registered
61
+ * property is written as a keyframe that sets it, i.e. `{ to: { "--angle": "360deg" } }`.
62
+ */
63
+ type KeyframeDeclarations = Properties & {
64
+ [customProperty: `--${string}`]: string | number;
65
+ };
16
66
  /**
17
67
  * Provides users with an easy way to configure the major/most-often configurable
18
68
  * aspect of a design system, i.e. the palette, fonts, and increments.
@@ -56,6 +106,11 @@ interface Config {
56
106
  * `Css.setVar({ [Tokens.X]: … })` at build time (web target).
57
107
  */
58
108
  tokens?: TokenRegistry;
109
+ /**
110
+ * Optional `@keyframes` animations: emitted as a `Keyframes` enum in generated `Css.ts` (web
111
+ * target), and written to the stylesheet only while some rule still names them.
112
+ */
113
+ keyframes?: KeyframesConfig;
59
114
  /**
60
115
  * Which default methods to include.
61
116
  *
@@ -290,4 +345,4 @@ declare function tryParseIncrementCalcMultiplier(cssValue: string): string | nul
290
345
  /** Prepended to emitted Truss CSS; `incrementPx` comes from `truss-config` / `Css.json`. */
291
346
  declare function rootSpacingPreludeCss(incrementPx: number): string;
292
347
 
293
- export { type Aliases, type Config, type CreateMethodsFn, type FontConfig, type IncConfig, SPACING_CUSTOM_PROPERTY, type SectionName, type Sections, type TokenRegistry, type UtilityMethod, type UtilityName, type WebEntry, defaultSections, defineConfig, generate, incrementCssValue, newAliasesMethods, newCoreIncrementMethods, newIncrementMethods, newMethod, newMethodsForProp, newParamMethod, newPxMethod, newSetCssVariablesMethod, rootSpacingPreludeCss, startWebCollection, stopWebCollection, tryParseIncrementCalcMultiplier };
348
+ export { type Aliases, type Config, type CreateMethodsFn, type FontConfig, type IncConfig, type KeyframeDeclarations, type KeyframesConfig, SPACING_CUSTOM_PROPERTY, type SectionName, type Sections, type TokenDefinition, type TokenRegistry, type UtilityMethod, type UtilityName, type WebEntry, defaultSections, defineConfig, generate, incrementCssValue, newAliasesMethods, newCoreIncrementMethods, newIncrementMethods, newMethod, newMethodsForProp, newParamMethod, newPxMethod, newSetCssVariablesMethod, rootSpacingPreludeCss, startWebCollection, stopWebCollection, tryParseIncrementCalcMultiplier };
package/build/index.js CHANGED
@@ -586,6 +586,9 @@ function lowerCaseFirst(s) {
586
586
  function quote(s) {
587
587
  return JSON.stringify(s);
588
588
  }
589
+ function camelToKebab(s) {
590
+ return s.replace(/^(Webkit|Moz|Ms|O)/, (m) => `-${m.toLowerCase()}`).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
591
+ }
589
592
 
590
593
  // src/sections/tachyons/skins.ts
591
594
  var skins = (config) => {
@@ -1013,6 +1016,59 @@ var TRUSS_PSEUDO_METHODS = {
1013
1016
  ifLastOfType: ":last-of-type"
1014
1017
  };
1015
1018
 
1019
+ // src/at-rules.ts
1020
+ function tokenVarName(token) {
1021
+ return typeof token === "string" ? token : token.var;
1022
+ }
1023
+ function tokenDefinition(token) {
1024
+ return typeof token === "string" ? void 0 : token;
1025
+ }
1026
+ function tokenVarNames(tokens) {
1027
+ const names = {};
1028
+ for (const [name, token] of Object.entries(tokens ?? {})) {
1029
+ names[name] = tokenVarName(token);
1030
+ }
1031
+ return names;
1032
+ }
1033
+ function tokenPropertyBlocks(tokens) {
1034
+ const blocks = {};
1035
+ for (const [name, token] of Object.entries(tokens ?? {})) {
1036
+ const definition = tokenDefinition(token);
1037
+ if (!definition) continue;
1038
+ if (definition.syntax !== "*" && definition.initialValue === void 0) {
1039
+ throw new Error(
1040
+ `Token "${name}" has syntax ${JSON.stringify(definition.syntax)} but no initialValue. @property requires an initial value for every syntax except "*".`
1041
+ );
1042
+ }
1043
+ blocks[definition.var] = propertyCssText(definition);
1044
+ }
1045
+ return blocks;
1046
+ }
1047
+ function propertyCssText(definition) {
1048
+ const descriptors = [`syntax: ${JSON.stringify(definition.syntax)}`, `inherits: ${definition.inherits ?? false}`];
1049
+ if (definition.initialValue !== void 0) {
1050
+ descriptors.push(`initial-value: ${definition.initialValue}`);
1051
+ }
1052
+ return `@property ${definition.var} { ${descriptors.join("; ")}; }`;
1053
+ }
1054
+ function keyframesBlocks(keyframes) {
1055
+ const blocks = {};
1056
+ for (const [name, frames] of Object.entries(keyframes ?? {})) {
1057
+ blocks[name] = frames === null ? "" : keyframesCssText(name, frames);
1058
+ }
1059
+ return blocks;
1060
+ }
1061
+ function keyframesCssText(name, frames) {
1062
+ if (typeof frames === "string") {
1063
+ return `@keyframes ${name} { ${frames.trim().replace(/\s+/g, " ")} }`;
1064
+ }
1065
+ const steps = Object.entries(frames).map(([selector, properties]) => {
1066
+ const declarations = Object.entries(properties).filter((entry) => entry[1] !== void 0).map((entry) => `${camelToKebab(entry[0])}: ${entry[1]};`).join(" ");
1067
+ return `${selector} { ${declarations} }`;
1068
+ });
1069
+ return `@keyframes ${name} { ${steps.join(" ")} }`;
1070
+ }
1071
+
1016
1072
  // src/generate.ts
1017
1073
  var CssProperties = imp("t:Properties@csstype");
1018
1074
  var defaultTypeAliases = {
@@ -1020,7 +1076,7 @@ var defaultTypeAliases = {
1020
1076
  Padding: ["padding", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft"]
1021
1077
  };
1022
1078
  function emitTokensEnumAndSetVarTypes(tokens) {
1023
- const entries = tokens && typeof tokens === "object" ? Object.entries(tokens).filter(([, v]) => typeof v === "string") : [];
1079
+ const entries = Object.entries(tokenVarNames(tokens));
1024
1080
  const hasTokens = entries.length > 0;
1025
1081
  const enumBlock = hasTokens ? `export enum Tokens {
1026
1082
  ${entries.map(([name, value]) => ` ${name} = ${JSON.stringify(value)},`).join("\n")}
@@ -1046,6 +1102,16 @@ ${entries.map(([name, value]) => ` ${name} = ${JSON.stringify(value)},`).join("
1046
1102
  `;
1047
1103
  return enumBlock + keysType + scalar + valueType;
1048
1104
  }
1105
+ function emitKeyframesEnum(keyframes) {
1106
+ const names = Object.keys(keyframes ?? {});
1107
+ if (names.length === 0) return "";
1108
+ const members = names.map((name) => ` ${pascalCase(name)} = ${JSON.stringify(name)},`).join("\n");
1109
+ return `export enum Keyframes {
1110
+ ${members}
1111
+ }
1112
+
1113
+ `;
1114
+ }
1049
1115
  async function generate(config) {
1050
1116
  const { outputPath } = config;
1051
1117
  const target = config.target ?? "web";
@@ -1442,7 +1508,7 @@ export type ${def("RuntimeStyles")} = RawCssProperties & { readonly __kind: "run
1442
1508
  ${typographyType}
1443
1509
 
1444
1510
  ${emitTokensEnumAndSetVarTypes(tokens)}
1445
-
1511
+ ${emitKeyframesEnum(config.keyframes)}
1446
1512
  // Augment React types so all JSX elements accept the \`css\` prop:
1447
1513
  // - HTMLAttributes/SVGAttributes cover intrinsic elements (div, svg, etc.)
1448
1514
  // - JSX.IntrinsicAttributes covers custom components (Card, Page, etc.)
@@ -1769,12 +1835,16 @@ function generateTrussMapping(config, entries) {
1769
1835
  for (const [name, mediaQuery] of Object.entries(genBreakpointsMap)) {
1770
1836
  breakpointEntries[`if${pascalCase(name)}`] = mediaQuery;
1771
1837
  }
1772
- const tokenEntries = config.tokens && Object.keys(config.tokens).length > 0 ? config.tokens : void 0;
1838
+ const tokenEntries = tokenVarNames(config.tokens);
1839
+ const propertyEntries = tokenPropertyBlocks(config.tokens);
1840
+ const keyframeEntries = keyframesBlocks(config.keyframes);
1773
1841
  return {
1774
1842
  increment: config.increment,
1775
1843
  ...Object.keys(breakpointEntries).length > 0 ? { breakpoints: breakpointEntries } : {},
1776
1844
  ...Object.keys(config.fonts).length > 0 ? { typography: Object.keys(config.fonts) } : {},
1777
- ...tokenEntries ? { tokens: tokenEntries } : {},
1845
+ ...Object.keys(tokenEntries).length > 0 ? { tokens: tokenEntries } : {},
1846
+ ...Object.keys(propertyEntries).length > 0 ? { properties: propertyEntries } : {},
1847
+ ...Object.keys(keyframeEntries).length > 0 ? { keyframes: keyframeEntries } : {},
1778
1848
  abbreviations
1779
1849
  };
1780
1850
  }
@@ -1791,6 +1861,12 @@ function condensedJson(mapping) {
1791
1861
  if (mapping.tokens && Object.keys(mapping.tokens).length > 0) {
1792
1862
  lines.push(` "tokens": ${JSON.stringify(mapping.tokens)},`);
1793
1863
  }
1864
+ if (mapping.properties && Object.keys(mapping.properties).length > 0) {
1865
+ lines.push(` "properties": ${JSON.stringify(mapping.properties)},`);
1866
+ }
1867
+ if (mapping.keyframes && Object.keys(mapping.keyframes).length > 0) {
1868
+ lines.push(` "keyframes": ${JSON.stringify(mapping.keyframes)},`);
1869
+ }
1794
1870
  lines.push(` "abbreviations": {`);
1795
1871
  const entries = Object.entries(mapping.abbreviations);
1796
1872
  for (let i = 0; i < entries.length; i++) {