@servicetitan/hammer-token 0.0.0-rc-1.48.0-20251104214834 → 0.0.0-rc-3.0.0-20260127161418

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/README.md +222 -0
  3. package/build/web/core/component-variables.scss +176 -130
  4. package/build/web/core/component.d.ts +96 -0
  5. package/build/web/core/component.js +574 -252
  6. package/build/web/core/component.scss +94 -69
  7. package/build/web/core/css-utils/a2-border.css +39 -41
  8. package/build/web/core/css-utils/a2-color.css +367 -227
  9. package/build/web/core/css-utils/a2-font.css +0 -2
  10. package/build/web/core/css-utils/a2-spacing.css +0 -2
  11. package/build/web/core/css-utils/a2-utils.css +434 -292
  12. package/build/web/core/css-utils/border.css +39 -41
  13. package/build/web/core/css-utils/color.css +367 -227
  14. package/build/web/core/css-utils/font.css +0 -2
  15. package/build/web/core/css-utils/spacing.css +0 -2
  16. package/build/web/core/css-utils/utils.css +434 -292
  17. package/build/web/core/index.d.ts +6 -0
  18. package/build/web/core/index.js +1 -1
  19. package/build/web/core/primitive-variables.scss +130 -71
  20. package/build/web/core/primitive.d.ts +185 -0
  21. package/build/web/core/primitive.js +328 -72
  22. package/build/web/core/primitive.scss +183 -124
  23. package/build/web/core/semantic-variables.scss +295 -220
  24. package/build/web/core/semantic.d.ts +198 -0
  25. package/build/web/core/semantic.js +913 -341
  26. package/build/web/core/semantic.scss +196 -140
  27. package/build/web/index.d.ts +3 -4
  28. package/build/web/index.js +0 -1
  29. package/build/web/types.d.ts +17 -0
  30. package/config.js +121 -496
  31. package/package.json +5 -4
  32. package/src/global/primitive/breakpoint.tokens.json +39 -0
  33. package/src/global/primitive/color.tokens.json +536 -0
  34. package/src/global/primitive/duration.tokens.json +32 -0
  35. package/src/global/primitive/font.tokens.json +103 -0
  36. package/src/global/primitive/radius.tokens.json +67 -0
  37. package/src/global/primitive/size.tokens.json +123 -0
  38. package/src/global/primitive/transition.tokens.json +20 -0
  39. package/src/theme/core/background.tokens.json +1058 -0
  40. package/src/theme/core/border.tokens.json +148 -0
  41. package/src/theme/core/charts.tokens.json +802 -0
  42. package/src/theme/core/component/alert.tokens.json +180 -0
  43. package/src/theme/core/component/announcement.tokens.json +186 -0
  44. package/src/theme/core/component/avatar.tokens.json +132 -0
  45. package/src/theme/core/component/badge.tokens.json +40 -0
  46. package/src/theme/core/component/button.tokens.json +752 -0
  47. package/src/theme/core/component/checkbox.tokens.json +292 -0
  48. package/src/theme/core/focus.tokens.json +48 -0
  49. package/src/theme/core/foreground.tokens.json +306 -0
  50. package/src/theme/core/shadow.tokens.json +43 -0
  51. package/src/theme/core/status.tokens.json +70 -0
  52. package/src/theme/core/typography.tokens.json +100 -0
  53. package/src/utils/copy-css-utils-cli.js +13 -0
  54. package/src/utils/css-utils-format-utils.js +98 -1
  55. package/src/utils/sd-build-configs.js +372 -0
  56. package/src/utils/sd-formats.js +752 -0
  57. package/src/utils/sd-transforms.js +126 -0
  58. package/src/utils/token-helpers.js +555 -0
  59. package/tsconfig.json +18 -0
  60. package/.turbo/turbo-build.log +0 -37
  61. package/build/web/core/raw.js +0 -234
  62. package/src/global/primitive/breakpoint.js +0 -19
  63. package/src/global/primitive/color.js +0 -231
  64. package/src/global/primitive/duration.js +0 -16
  65. package/src/global/primitive/font.js +0 -60
  66. package/src/global/primitive/radius.js +0 -31
  67. package/src/global/primitive/size.js +0 -55
  68. package/src/global/primitive/transition.js +0 -16
  69. package/src/theme/core/background.js +0 -170
  70. package/src/theme/core/border.js +0 -103
  71. package/src/theme/core/charts.js +0 -464
  72. package/src/theme/core/component/button.js +0 -708
  73. package/src/theme/core/component/checkbox.js +0 -405
  74. package/src/theme/core/focus.js +0 -35
  75. package/src/theme/core/foreground.js +0 -148
  76. package/src/theme/core/overlay.js +0 -137
  77. package/src/theme/core/shadow.js +0 -29
  78. package/src/theme/core/status.js +0 -49
  79. package/src/theme/core/typography.js +0 -82
  80. package/type/types.ts +0 -344
@@ -0,0 +1,126 @@
1
+ /* eslint-disable @typescript-eslint/no-require-imports */
2
+ /**
3
+ * Style Dictionary transform registrations
4
+ * @module sd-transforms
5
+ */
6
+ const {
7
+ getDtcgValue,
8
+ getTokenType,
9
+ isCompositeColor,
10
+ isDimensionValue,
11
+ } = require("./token-helpers");
12
+
13
+ /**
14
+ * Registers all custom Style Dictionary transforms for DTCG token format.
15
+ * This includes transforms for token names, values, cubic-bezier functions, and color opacity.
16
+ * Also registers a "dtcg" transform group that combines all these transforms.
17
+ * @param {import('style-dictionary').default} StyleDictionary - The Style Dictionary class instance
18
+ * @returns {void}
19
+ * @example
20
+ * const StyleDictionary = require('style-dictionary');
21
+ * registerTransforms(StyleDictionary);
22
+ */
23
+ const registerTransforms = (StyleDictionary) => {
24
+ /**
25
+ * Transform: dtcg/name
26
+ * Generates a clean token name from the token path, filtering out $root, $schema,
27
+ * and other $-prefixed segments.
28
+ */
29
+ StyleDictionary.registerTransform({
30
+ name: "dtcg/name",
31
+ type: "name",
32
+ transform: (token) => {
33
+ const tokenPath =
34
+ token.path || token.original?.path || token.pathSegments;
35
+
36
+ if (Array.isArray(tokenPath) && tokenPath.length > 0) {
37
+ // Filter out $root, $schema, and other $-prefixed segments from path
38
+ const filteredPath = tokenPath.filter(
39
+ (segment) =>
40
+ segment !== "$root" &&
41
+ segment !== "$schema" &&
42
+ !String(segment).startsWith("$"),
43
+ );
44
+ if (filteredPath.length > 0) {
45
+ return filteredPath.join("-");
46
+ }
47
+ }
48
+ if (token.name && typeof token.name === "string") {
49
+ // Clean up name: remove $root, $schema, collapse dashes
50
+ return token.name
51
+ .replace(/\$root/g, "")
52
+ .replace(/\$schema/g, "")
53
+ .replace(/\$\$+/g, "-")
54
+ .replace(/-+/g, "-")
55
+ .replace(/^-|-$/g, "");
56
+ }
57
+ return "unknown";
58
+ },
59
+ });
60
+
61
+ /**
62
+ * Transform: dtcg/value
63
+ * Extracts the DTCG $value from a token, handling dimension objects with value/unit.
64
+ */
65
+ StyleDictionary.registerTransform({
66
+ name: "dtcg/value",
67
+ type: "value",
68
+ transform: (token) => {
69
+ const value = getDtcgValue(token);
70
+
71
+ // Handle new format { value, unit }
72
+ if (isDimensionValue(value)) {
73
+ return `${value.value}${value.unit}`;
74
+ }
75
+ return value;
76
+ },
77
+ });
78
+
79
+ /**
80
+ * Transform: dtcg/cubic-bezier
81
+ * Converts cubicBezier token arrays to CSS cubic-bezier() function strings.
82
+ */
83
+ StyleDictionary.registerTransform({
84
+ name: "dtcg/cubic-bezier",
85
+ type: "value",
86
+ matcher: (token) => getTokenType(token) === "cubicBezier",
87
+ transform: (token) => {
88
+ const value = getDtcgValue(token);
89
+ if (Array.isArray(value)) {
90
+ return `cubic-bezier(${value.join(", ")})`;
91
+ }
92
+ return value;
93
+ },
94
+ });
95
+
96
+ /**
97
+ * Transform: dtcg/color-opacity
98
+ * Handles composite color tokens that have separate color and alpha values.
99
+ */
100
+ StyleDictionary.registerTransform({
101
+ name: "dtcg/color-opacity",
102
+ type: "value",
103
+ matcher: (token) =>
104
+ getTokenType(token) === "color" && isCompositeColor(getDtcgValue(token)),
105
+ transform: (token) => getDtcgValue(token),
106
+ });
107
+
108
+ /**
109
+ * Transform Group: dtcg
110
+ * Combines all DTCG-related transforms plus the built-in color/css transform.
111
+ */
112
+ StyleDictionary.registerTransformGroup({
113
+ name: "dtcg",
114
+ transforms: [
115
+ "dtcg/name",
116
+ "dtcg/value",
117
+ "dtcg/cubic-bezier",
118
+ "dtcg/color-opacity",
119
+ "color/css",
120
+ ],
121
+ });
122
+ };
123
+
124
+ module.exports = {
125
+ registerTransforms,
126
+ };
@@ -0,0 +1,555 @@
1
+ /**
2
+ * Token helper functions for Style Dictionary DTCG format
3
+ * These functions handle token value extraction, resolution, and CSS variable chain building
4
+ */
5
+
6
+ /**
7
+ * Extracts the DTCG value from a token
8
+ * @param {Object} token - The token object
9
+ * @returns {*} The token's $value property
10
+ */
11
+ const getDtcgValue = (token) => token.$value;
12
+
13
+ /**
14
+ * Extracts the DTCG token type
15
+ * @param {Object} token - The token object
16
+ * @returns {string|undefined} The token's $type property
17
+ */
18
+ const getTokenType = (token) => token.$type;
19
+
20
+ /**
21
+ * Checks if a token has a dark mode value
22
+ * @param {Object} token - The token object
23
+ * @returns {boolean} True if the token has a dark mode value defined
24
+ */
25
+ const hasDarkValue = (token) =>
26
+ token.$extensions?.appearance?.dark?.$value !== undefined;
27
+
28
+ /**
29
+ * Gets the dark mode value from a token
30
+ * @param {Object} token - The token object
31
+ * @returns {*} The dark mode value, or undefined if not present
32
+ */
33
+ const getDarkValue = (token) => token.$extensions?.appearance?.dark?.$value;
34
+
35
+ /**
36
+ * Checks if a value is a composite color object (contains color and alpha properties)
37
+ * @param {*} value - The value to check
38
+ * @returns {boolean} True if the value is a composite color object
39
+ * @example
40
+ * isCompositeColor({ color: '#fff', alpha: 0.5 }) // true
41
+ * isCompositeColor('#fff') // false
42
+ */
43
+ const isCompositeColor = (value) =>
44
+ value &&
45
+ typeof value === "object" &&
46
+ !Array.isArray(value) &&
47
+ value.color !== undefined &&
48
+ value.alpha !== undefined;
49
+
50
+ /**
51
+ * Checks if a value is a dimension object (contains value and unit properties)
52
+ * @param {*} value - The value to check
53
+ * @returns {boolean} True if the value is a dimension object
54
+ * @example
55
+ * isDimensionValue({ value: 16, unit: 'px' }) // true
56
+ * isDimensionValue('16px') // false
57
+ */
58
+ const isDimensionValue = (value) =>
59
+ value &&
60
+ typeof value === "object" &&
61
+ !Array.isArray(value) &&
62
+ value.value !== undefined &&
63
+ value.unit !== undefined;
64
+
65
+ /**
66
+ * Wraps values containing commas in double quotes for SCSS map compatibility.
67
+ * SCSS maps use commas as entry separators, so values with commas need quoting.
68
+ * @param {*} value - The value to potentially wrap
69
+ * @returns {*} The wrapped value if it contains commas, otherwise the original value
70
+ * @example
71
+ * wrapScssValue('Arial, sans-serif') // '"Arial, sans-serif"'
72
+ * wrapScssValue('Arial') // 'Arial'
73
+ */
74
+ const wrapScssValue = (value) => {
75
+ if (typeof value !== "string") return value;
76
+ if (value.includes(",")) {
77
+ return `"${value}"`;
78
+ }
79
+ return value;
80
+ };
81
+
82
+ /**
83
+ * Gets the token name from its path, filtering out $root segments
84
+ * @param {Object} token - The token object
85
+ * @returns {string} The token name as a hyphen-separated string
86
+ * @example
87
+ * getTokenName({ path: ['color', 'primary', '500'] }) // 'color-primary-500'
88
+ */
89
+ const getTokenName = (token) => {
90
+ const tokenPath = token.path;
91
+ if (Array.isArray(tokenPath) && tokenPath.length > 0) {
92
+ // Filter out $root if it's the last segment
93
+ const filteredPath =
94
+ tokenPath[tokenPath.length - 1] === "$root"
95
+ ? tokenPath.slice(0, -1)
96
+ : tokenPath;
97
+ return filteredPath.join("-");
98
+ }
99
+ return "unknown";
100
+ };
101
+
102
+ /**
103
+ * Builds a Map of tokens keyed by their names for O(1) lookups
104
+ * @param {Object} dictionary - The Style Dictionary dictionary object
105
+ * @returns {Map<string, Object>} A Map where keys are token names and values are token objects
106
+ */
107
+ const buildTokenMap = (dictionary) =>
108
+ new Map(
109
+ (dictionary.unfilteredAllTokens || dictionary.allTokens).map((t) => [
110
+ getTokenName(t),
111
+ t,
112
+ ]),
113
+ );
114
+
115
+ /**
116
+ * Converts a hex color to hex8 format (with alpha channel)
117
+ * @param {string} hexColor - The hex color to convert (e.g., '#ff0000' or '#f00')
118
+ * @param {number} alpha - The alpha value between 0 and 1
119
+ * @returns {string} The hex8 color string (e.g., '#ff000080')
120
+ * @example
121
+ * convertToHex8('#ff0000', 0.5) // '#ff000080'
122
+ * convertToHex8('#f00', 1) // '#ff0000FF'
123
+ */
124
+ const convertToHex8 = (hexColor, alpha) => {
125
+ if (!hexColor || typeof hexColor !== "string" || !hexColor.startsWith("#")) {
126
+ return hexColor;
127
+ }
128
+
129
+ // Remove # and normalize to 6-digit hex
130
+ let hex = hexColor.replace("#", "");
131
+ if (hex.length === 3) {
132
+ // Expand shorthand: #RGB -> #RRGGBB
133
+ hex = hex
134
+ .split("")
135
+ .map((char) => char + char)
136
+ .join("");
137
+ } else if (hex.length !== 6) {
138
+ return hexColor; // Invalid hex format
139
+ }
140
+
141
+ // Convert alpha (0-1) to hex (00-FF)
142
+ const alphaHex = Math.round(alpha * 255)
143
+ .toString(16)
144
+ .padStart(2, "0")
145
+ .toUpperCase();
146
+
147
+ return `#${hex}${alphaHex}`;
148
+ };
149
+
150
+ /**
151
+ * Resolves a reference string using Style Dictionary utilities.
152
+ * If reference points to a group, tries appending .$root to find the token.
153
+ * @param {string} refString - The reference string to resolve (e.g., '{color.primary.500}')
154
+ * @param {Object} dictionary - The Style Dictionary dictionary object
155
+ * @param {Function} resolveReferences - The SD5 resolveReferences function
156
+ * @returns {*} The resolved value, or the original string if resolution fails
157
+ */
158
+ const resolveReference = (refString, dictionary, resolveReferences) => {
159
+ if (typeof refString !== "string") {
160
+ return refString;
161
+ }
162
+ try {
163
+ // 1. Try resolving exactly as requested (your suggestion step 1 & 2)
164
+ const result = resolveReferences(refString, dictionary.tokens, {
165
+ usesDtcg: true,
166
+ });
167
+
168
+ // 2. If it couldn't be resolved (it still contains braces), try with .$root (your suggestion step 3)
169
+ if (
170
+ typeof result === "string" &&
171
+ result.includes("{") &&
172
+ result.includes("}")
173
+ ) {
174
+ const withRoot = refString.replace(/\{([^{}]+)\}/g, (match, path) => {
175
+ // Only append .$root if it's not already there
176
+ return path.endsWith(".$root") ? match : `{${path}.$root}`;
177
+ });
178
+
179
+ try {
180
+ const rootResult = resolveReferences(withRoot, dictionary.tokens, {
181
+ usesDtcg: true,
182
+ });
183
+ // If the .$root version resolved successfully, use it
184
+ if (!rootResult.includes("{")) {
185
+ return rootResult;
186
+ }
187
+ } catch {
188
+ // Fallback to original result if .$root also fails
189
+ }
190
+ }
191
+ return result;
192
+ } catch {
193
+ return refString;
194
+ }
195
+ };
196
+
197
+ /**
198
+ * Resolves a composite color object (with color and alpha properties) to a hex8 string
199
+ * @param {Object} colorObj - The composite color object with color and alpha properties
200
+ * @param {Object} dictionary - The Style Dictionary dictionary object
201
+ * @param {Function} resolveReferences - The SD5 resolveReferences function
202
+ * @returns {string|null} The resolved hex8 color string, or null if not a composite color
203
+ */
204
+ const resolveCompositeColor = (colorObj, dictionary, resolveReferences) => {
205
+ if (!isCompositeColor(colorObj)) {
206
+ return null;
207
+ }
208
+ const colorValue = resolveReference(
209
+ colorObj.color,
210
+ dictionary,
211
+ resolveReferences,
212
+ );
213
+ if (typeof colorValue === "string" && colorValue.startsWith("#")) {
214
+ return convertToHex8(colorValue, colorObj.alpha);
215
+ }
216
+ return colorValue;
217
+ };
218
+
219
+ /**
220
+ * Gets the resolved token value, handling dark mode, composite colors, and references
221
+ * @param {Object} token - The token object
222
+ * @param {Object} dictionary - The Style Dictionary dictionary object
223
+ * @param {Object} [options] - Options for value resolution
224
+ * @param {boolean} [options.isDark=false] - Whether to get the dark mode value
225
+ * @param {boolean} [options.forScssMap=false] - Whether to format for SCSS map (quotes commas)
226
+ * @param {Function} resolveReferences - The SD5 resolveReferences function
227
+ * @returns {string} The resolved token value as a string
228
+ */
229
+ const getTokenValue = (
230
+ token,
231
+ dictionary,
232
+ { isDark = false, forScssMap = false } = {},
233
+ resolveReferences,
234
+ ) => {
235
+ let value = getDtcgValue(token);
236
+
237
+ // Handle dark mode
238
+ if (isDark && hasDarkValue(token)) {
239
+ value = getDarkValue(token);
240
+ }
241
+
242
+ // Handle composite colors
243
+ if (isCompositeColor(value)) {
244
+ const resolved = resolveCompositeColor(
245
+ value,
246
+ dictionary,
247
+ resolveReferences,
248
+ );
249
+ if (resolved) {
250
+ value = resolved;
251
+ }
252
+ }
253
+
254
+ // Handle dimension values { value, unit }
255
+ if (isDimensionValue(value)) {
256
+ value = `${value.value}${value.unit}`;
257
+ }
258
+
259
+ // Resolve references
260
+ if (typeof value === "string" && value.includes("{") && value.includes("}")) {
261
+ value = resolveReference(value, dictionary, resolveReferences);
262
+ }
263
+
264
+ // Convert to string and clean up
265
+ if (value === undefined || value === null) {
266
+ return "";
267
+ }
268
+ const valueStr = typeof value === "string" ? value : JSON.stringify(value);
269
+
270
+ // Normalize transparent to rgba for consistency
271
+ const cleaned = valueStr.replaceAll('"', "");
272
+ if (cleaned === "transparent") {
273
+ return "rgba(0, 0, 0, 0)";
274
+ }
275
+
276
+ // Wrap values with commas in quotes for SCSS map compatibility
277
+ if (forScssMap) {
278
+ return wrapScssValue(cleaned);
279
+ }
280
+ return cleaned;
281
+ };
282
+
283
+ /**
284
+ * Checks if a value is a reference string using the usesReferences function
285
+ * @param {*} value - The value to check
286
+ * @param {Function} usesReferences - The SD5 usesReferences function
287
+ * @returns {boolean} True if the value is a reference string
288
+ */
289
+ const isReferenceString = (value, usesReferences) =>
290
+ typeof value === "string" && usesReferences(value);
291
+
292
+ /**
293
+ * Builds a recursive CSS var chain with light-dark() at the level where light/dark values differ.
294
+ * Creates nested var() fallbacks that ultimately resolve to light-dark() at the primitive level.
295
+ * @param {string} originalValue - The original reference string (e.g., '{status.color.danger}')
296
+ * @param {Map<string, Object>} tokenMap - Map of token names to token objects
297
+ * @param {Object} dictionary - The Style Dictionary dictionary object
298
+ * @param {boolean} [useLightDark=false] - Whether to include light-dark() for color variants
299
+ * @param {Function} usesReferences - The SD5 usesReferences function
300
+ * @param {Function} resolveReferences - The SD5 resolveReferences function
301
+ * @param {string} [cssVarPrefix=''] - Prefix for CSS variable names (e.g., 'a2-')
302
+ * @param {boolean} [forScssMap=false] - Whether to format for SCSS map
303
+ * @returns {string} The CSS var chain string
304
+ * @example
305
+ * // Returns: 'var(--a2-status-color-danger, light-dark(var(--a2-color-red-500, #e13212), var(--a2-color-red-100, #ff745f)))'
306
+ * buildRecursiveVarChainWithLightDark('{status.color.danger}', tokenMap, dictionary, true, usesReferences, resolveReferences, 'a2-')
307
+ */
308
+ const buildRecursiveVarChainWithLightDark = (
309
+ originalValue,
310
+ tokenMap,
311
+ dictionary,
312
+ useLightDark = false,
313
+ usesReferences,
314
+ resolveReferences,
315
+ cssVarPrefix = "",
316
+ forScssMap = false,
317
+ ) => {
318
+ // If not a reference string, return the value as-is
319
+ if (
320
+ !originalValue ||
321
+ typeof originalValue !== "string" ||
322
+ !usesReferences(originalValue)
323
+ ) {
324
+ return originalValue;
325
+ }
326
+
327
+ // Replace each {reference} with var(--name, recursiveResult)
328
+ return originalValue.replace(/\{([^{}]+)\}/g, (_, refPath) => {
329
+ const refTokenName = refPath.replace(/\./g, "-");
330
+ const refToken = tokenMap.get(refTokenName);
331
+ if (!refToken) return `var(--${cssVarPrefix}${refTokenName})`;
332
+
333
+ const refOriginalLight = refToken.original?.$value;
334
+ const refOriginalDark =
335
+ refToken.original?.$extensions?.appearance?.dark?.$value;
336
+ const refHasDarkVariant = refOriginalDark !== undefined;
337
+
338
+ // Check if light and dark values are DIFFERENT - this is where light-dark() should go
339
+ const lightDarkDiffer =
340
+ refHasDarkVariant && refOriginalLight !== refOriginalDark;
341
+
342
+ if (useLightDark && lightDarkDiffer) {
343
+ // Light and dark values differ - insert light-dark() here
344
+ const lightResult = buildRecursiveVarChainWithLightDark(
345
+ refOriginalLight,
346
+ tokenMap,
347
+ dictionary,
348
+ false,
349
+ usesReferences,
350
+ resolveReferences,
351
+ cssVarPrefix,
352
+ forScssMap,
353
+ );
354
+ // Same fix as darkFinal: ensure lightResult is a string before using it
355
+ const lightFinal =
356
+ typeof lightResult === "string" && usesReferences(refOriginalLight)
357
+ ? lightResult
358
+ : getTokenValue(
359
+ refToken,
360
+ dictionary,
361
+ { isDark: false, forScssMap },
362
+ resolveReferences,
363
+ );
364
+
365
+ const darkResult = buildRecursiveVarChainWithLightDark(
366
+ refOriginalDark,
367
+ tokenMap,
368
+ dictionary,
369
+ false,
370
+ usesReferences,
371
+ resolveReferences,
372
+ cssVarPrefix,
373
+ forScssMap,
374
+ );
375
+ // Ensure darkResult is a string (resolved var chain), not just truthy.
376
+ // Composite color objects pass through buildRecursiveVarChainWithLightDark unchanged,
377
+ // but usesReferences() returns true for them (checks nested properties).
378
+ const darkFinal =
379
+ typeof darkResult === "string" && usesReferences(refOriginalDark)
380
+ ? darkResult
381
+ : getTokenValue(
382
+ refToken,
383
+ dictionary,
384
+ { isDark: true, forScssMap },
385
+ resolveReferences,
386
+ );
387
+
388
+ return `var(--${cssVarPrefix}${refTokenName}, light-dark(${lightFinal}, ${darkFinal}))`;
389
+ }
390
+
391
+ // Light and dark are same (or no dark) - continue recursing, propagate useLightDark
392
+ if (
393
+ refOriginalLight &&
394
+ typeof refOriginalLight === "string" &&
395
+ usesReferences(refOriginalLight)
396
+ ) {
397
+ const nestedResult = buildRecursiveVarChainWithLightDark(
398
+ refOriginalLight,
399
+ tokenMap,
400
+ dictionary,
401
+ useLightDark,
402
+ usesReferences,
403
+ resolveReferences,
404
+ cssVarPrefix,
405
+ forScssMap,
406
+ );
407
+ return `var(--${cssVarPrefix}${refTokenName}, ${nestedResult})`;
408
+ }
409
+
410
+ // Final value (primitive) - get resolved value
411
+ const finalValue = getTokenValue(
412
+ refToken,
413
+ dictionary,
414
+ { isDark: false, forScssMap },
415
+ resolveReferences,
416
+ );
417
+ return `var(--${cssVarPrefix}${refTokenName}, ${finalValue})`;
418
+ });
419
+ };
420
+
421
+ /**
422
+ * Builds a fallback value with recursive refs and light-dark() at the appropriate level.
423
+ * This is the main entry point for generating CSS fallback values with dark mode support.
424
+ * @param {Object} token - The token object
425
+ * @param {Object} dictionary - The Style Dictionary dictionary object
426
+ * @param {Map<string, Object>} tokenMap - Map of token names to token objects
427
+ * @param {Object} [options] - Options for fallback building
428
+ * @param {boolean} [options.isDark=false] - Whether to get the dark mode value
429
+ * @param {boolean} [options.useLightDark=false] - Whether to include light-dark() for color variants
430
+ * @param {string} [options.cssVarPrefix=''] - Prefix for CSS variable names
431
+ * @param {boolean} [options.forScssMap=false] - Whether to format for SCSS map
432
+ * @param {Function} usesReferences - The SD5 usesReferences function
433
+ * @param {Function} resolveReferences - The SD5 resolveReferences function
434
+ * @returns {string} The fallback value string with CSS var chains and/or light-dark()
435
+ */
436
+ const buildFallbackWithRefs = (
437
+ token,
438
+ dictionary,
439
+ tokenMap,
440
+ {
441
+ isDark = false,
442
+ useLightDark = false,
443
+ cssVarPrefix = "",
444
+ forScssMap = false,
445
+ } = {},
446
+ usesReferences,
447
+ resolveReferences,
448
+ ) => {
449
+ const originalValue = token.original?.$value;
450
+ const originalDark = token.original?.$extensions?.appearance?.dark?.$value;
451
+ const hasDark = originalDark !== undefined;
452
+
453
+ // Check if this token's light/dark values are different at top level
454
+ // Use JSON.stringify for object comparison (composite colors)
455
+ const lightDarkDifferAtTop =
456
+ hasDark && JSON.stringify(originalValue) !== JSON.stringify(originalDark);
457
+
458
+ // If useLightDark and light/dark differ at this level, insert light-dark() here
459
+ if (useLightDark && lightDarkDifferAtTop) {
460
+ // Handle light value - could be a string reference or composite object
461
+ let lightFinal;
462
+ if (isReferenceString(originalValue, usesReferences)) {
463
+ lightFinal = buildRecursiveVarChainWithLightDark(
464
+ originalValue,
465
+ tokenMap,
466
+ dictionary,
467
+ false,
468
+ usesReferences,
469
+ resolveReferences,
470
+ cssVarPrefix,
471
+ forScssMap,
472
+ );
473
+ } else {
474
+ // Composite color or plain value - resolve directly
475
+ lightFinal = getTokenValue(
476
+ token,
477
+ dictionary,
478
+ { isDark: false, forScssMap },
479
+ resolveReferences,
480
+ );
481
+ }
482
+
483
+ // Handle dark value - could be a string reference or composite object
484
+ let darkFinal;
485
+ if (isReferenceString(originalDark, usesReferences)) {
486
+ darkFinal = buildRecursiveVarChainWithLightDark(
487
+ originalDark,
488
+ tokenMap,
489
+ dictionary,
490
+ false,
491
+ usesReferences,
492
+ resolveReferences,
493
+ cssVarPrefix,
494
+ forScssMap,
495
+ );
496
+ } else {
497
+ // Composite color or plain value - resolve directly
498
+ darkFinal = getTokenValue(
499
+ token,
500
+ dictionary,
501
+ { isDark: true, forScssMap },
502
+ resolveReferences,
503
+ );
504
+ }
505
+
506
+ return `light-dark(${lightFinal}, ${darkFinal})`;
507
+ }
508
+
509
+ // Light/dark same or no dark - recurse and propagate useLightDark to find where they differ
510
+ // When isDark is true and we have a dark value, use originalDark instead of originalValue
511
+ const valueToProcess = isDark && hasDark ? originalDark : originalValue;
512
+
513
+ if (isReferenceString(valueToProcess, usesReferences)) {
514
+ const result = buildRecursiveVarChainWithLightDark(
515
+ valueToProcess,
516
+ tokenMap,
517
+ dictionary,
518
+ useLightDark,
519
+ usesReferences,
520
+ resolveReferences,
521
+ cssVarPrefix,
522
+ forScssMap,
523
+ );
524
+ if (result && result !== valueToProcess) {
525
+ return result;
526
+ }
527
+ }
528
+
529
+ // No references or composite value - return the resolved final value
530
+ return getTokenValue(
531
+ token,
532
+ dictionary,
533
+ { isDark, forScssMap },
534
+ resolveReferences,
535
+ );
536
+ };
537
+
538
+ module.exports = {
539
+ getDtcgValue,
540
+ getTokenType,
541
+ hasDarkValue,
542
+ getDarkValue,
543
+ isCompositeColor,
544
+ isDimensionValue,
545
+ wrapScssValue,
546
+ getTokenName,
547
+ buildTokenMap,
548
+ convertToHex8,
549
+ resolveReference,
550
+ resolveCompositeColor,
551
+ getTokenValue,
552
+ isReferenceString,
553
+ buildRecursiveVarChainWithLightDark,
554
+ buildFallbackWithRefs,
555
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020"],
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "moduleResolution": "node",
11
+ "resolveJsonModule": true,
12
+ "allowJs": true,
13
+ "checkJs": false,
14
+ "noEmit": true
15
+ },
16
+ "include": ["src/**/*.js", "config.js"],
17
+ "exclude": ["node_modules", "build", "build-reference"]
18
+ }