@servicetitan/hammer-token 2.5.0 → 3.0.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/CHANGELOG.md +56 -0
- package/README.md +332 -0
- package/build/web/core/component-variables.scss +1088 -131
- package/build/web/core/component.d.ts +558 -0
- package/build/web/core/component.js +6685 -249
- package/build/web/core/component.scss +557 -69
- package/build/web/core/css-utils/a2-border.css +47 -45
- package/build/web/core/css-utils/a2-color.css +443 -227
- package/build/web/core/css-utils/a2-font.css +0 -2
- package/build/web/core/css-utils/a2-spacing.css +476 -478
- package/build/web/core/css-utils/a2-utils.css +992 -772
- package/build/web/core/css-utils/border.css +47 -45
- package/build/web/core/css-utils/color.css +443 -227
- package/build/web/core/css-utils/font.css +0 -2
- package/build/web/core/css-utils/spacing.css +476 -478
- package/build/web/core/css-utils/utils.css +992 -772
- package/build/web/core/index.d.ts +6 -0
- package/build/web/core/index.js +1 -1
- package/build/web/core/primitive-variables.scss +148 -65
- package/build/web/core/primitive.d.ts +209 -0
- package/build/web/core/primitive.js +779 -61
- package/build/web/core/primitive.scss +207 -124
- package/build/web/core/semantic-variables.scss +363 -239
- package/build/web/core/semantic.d.ts +221 -0
- package/build/web/core/semantic.js +1613 -347
- package/build/web/core/semantic.scss +219 -137
- package/build/web/index.d.ts +3 -4
- package/build/web/index.js +0 -1
- package/build/web/types.d.ts +17 -0
- package/config.js +121 -497
- package/eslint.config.mjs +11 -1
- package/package.json +15 -5
- package/src/global/primitive/breakpoint.tokens.json +54 -0
- package/src/global/primitive/color.tokens.json +1092 -0
- package/src/global/primitive/duration.tokens.json +44 -0
- package/src/global/primitive/font.tokens.json +151 -0
- package/src/global/primitive/radius.tokens.json +94 -0
- package/src/global/primitive/size.tokens.json +174 -0
- package/src/global/primitive/transition.tokens.json +32 -0
- package/src/theme/core/background.tokens.json +1312 -0
- package/src/theme/core/border.tokens.json +192 -0
- package/src/theme/core/chart.tokens.json +982 -0
- package/src/theme/core/component/ai-mark.tokens.json +20 -0
- package/src/theme/core/component/alert.tokens.json +261 -0
- package/src/theme/core/component/announcement.tokens.json +460 -0
- package/src/theme/core/component/avatar.tokens.json +137 -0
- package/src/theme/core/component/badge.tokens.json +42 -0
- package/src/theme/core/component/breadcrumb.tokens.json +42 -0
- package/src/theme/core/component/button-toggle.tokens.json +428 -0
- package/src/theme/core/component/button.tokens.json +941 -0
- package/src/theme/core/component/calendar.tokens.json +391 -0
- package/src/theme/core/component/card.tokens.json +107 -0
- package/src/theme/core/component/checkbox.tokens.json +631 -0
- package/src/theme/core/component/chip.tokens.json +169 -0
- package/src/theme/core/component/combobox.tokens.json +269 -0
- package/src/theme/core/component/details.tokens.json +152 -0
- package/src/theme/core/component/dialog.tokens.json +87 -0
- package/src/theme/core/component/divider.tokens.json +23 -0
- package/src/theme/core/component/dnd.tokens.json +208 -0
- package/src/theme/core/component/drawer.tokens.json +61 -0
- package/src/theme/core/component/drilldown.tokens.json +61 -0
- package/src/theme/core/component/edit-card.tokens.json +381 -0
- package/src/theme/core/component/field-label.tokens.json +42 -0
- package/src/theme/core/component/field-message.tokens.json +74 -0
- package/src/theme/core/component/icon.tokens.json +42 -0
- package/src/theme/core/component/link.tokens.json +108 -0
- package/src/theme/core/component/list-view.tokens.json +82 -0
- package/src/theme/core/component/listbox.tokens.json +283 -0
- package/src/theme/core/component/menu.tokens.json +230 -0
- package/src/theme/core/component/overflow.tokens.json +84 -0
- package/src/theme/core/component/page.tokens.json +377 -0
- package/src/theme/core/component/pagination.tokens.json +63 -0
- package/src/theme/core/component/popover.tokens.json +122 -0
- package/src/theme/core/component/progress-bar.tokens.json +133 -0
- package/src/theme/core/component/radio.tokens.json +631 -0
- package/src/theme/core/component/segmented-control.tokens.json +175 -0
- package/src/theme/core/component/select-card.tokens.json +943 -0
- package/src/theme/core/component/side-nav.tokens.json +349 -0
- package/src/theme/core/component/skeleton.tokens.json +42 -0
- package/src/theme/core/component/spinner.tokens.json +96 -0
- package/src/theme/core/component/status-icon.tokens.json +164 -0
- package/src/theme/core/component/stepper.tokens.json +484 -0
- package/src/theme/core/component/switch.tokens.json +285 -0
- package/src/theme/core/component/tab.tokens.json +192 -0
- package/src/theme/core/component/text-field.tokens.json +160 -0
- package/src/theme/core/component/text.tokens.json +59 -0
- package/src/theme/core/component/toast.tokens.json +343 -0
- package/src/theme/core/component/toolbar.tokens.json +114 -0
- package/src/theme/core/component/tooltip.tokens.json +61 -0
- package/src/theme/core/focus.tokens.json +56 -0
- package/src/theme/core/foreground.tokens.json +416 -0
- package/src/theme/core/gradient.tokens.json +41 -0
- package/src/theme/core/opacity.tokens.json +25 -0
- package/src/theme/core/shadow.tokens.json +81 -0
- package/src/theme/core/status.tokens.json +74 -0
- package/src/theme/core/typography.tokens.json +163 -0
- package/src/utils/__tests__/css-utils-format-utils.test.js +312 -0
- package/src/utils/__tests__/sd-build-configs.test.js +306 -0
- package/src/utils/__tests__/sd-formats.test.js +950 -0
- package/src/utils/__tests__/sd-transforms.test.js +336 -0
- package/src/utils/__tests__/token-helpers.test.js +1160 -0
- package/src/utils/copy-css-utils-cli.js +13 -1
- package/src/utils/css-utils-format-utils.js +105 -176
- package/src/utils/figma/__tests__/sync-gradient.test.js +561 -0
- package/src/utils/figma/__tests__/token-conversion.test.js +117 -0
- package/src/utils/figma/__tests__/token-resolution.test.js +231 -0
- package/src/utils/figma/auth.js +355 -0
- package/src/utils/figma/constants.js +22 -0
- package/src/utils/figma/errors.js +80 -0
- package/src/utils/figma/figma-api.js +1069 -0
- package/src/utils/figma/get-token.js +348 -0
- package/src/utils/figma/sync-components.js +909 -0
- package/src/utils/figma/sync-main.js +692 -0
- package/src/utils/figma/sync-orchestration.js +683 -0
- package/src/utils/figma/sync-primitives.js +230 -0
- package/src/utils/figma/sync-semantic.js +1056 -0
- package/src/utils/figma/token-conversion.js +340 -0
- package/src/utils/figma/token-parsing.js +186 -0
- package/src/utils/figma/token-resolution.js +569 -0
- package/src/utils/figma/utils.js +199 -0
- package/src/utils/sd-build-configs.js +305 -0
- package/src/utils/sd-formats.js +965 -0
- package/src/utils/sd-transforms.js +165 -0
- package/src/utils/token-helpers.js +848 -0
- package/tsconfig.json +18 -0
- package/vitest.config.js +17 -0
- package/.turbo/turbo-build.log +0 -37
- package/build/web/core/raw.js +0 -229
- package/src/global/primitive/breakpoint.js +0 -19
- package/src/global/primitive/color.js +0 -231
- package/src/global/primitive/duration.js +0 -16
- package/src/global/primitive/font.js +0 -60
- package/src/global/primitive/radius.js +0 -31
- package/src/global/primitive/size.js +0 -55
- package/src/global/primitive/transition.js +0 -16
- package/src/theme/core/background.js +0 -170
- package/src/theme/core/border.js +0 -103
- package/src/theme/core/charts.js +0 -439
- package/src/theme/core/component/button.js +0 -708
- package/src/theme/core/component/checkbox.js +0 -405
- package/src/theme/core/focus.js +0 -35
- package/src/theme/core/foreground.js +0 -148
- package/src/theme/core/overlay.js +0 -137
- package/src/theme/core/shadow.js +0 -29
- package/src/theme/core/status.js +0 -49
- package/src/theme/core/typography.js +0 -82
- package/type/types.ts +0 -341
|
@@ -0,0 +1,848 @@
|
|
|
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
|
+
* Extracts the DTCG description from a token
|
|
22
|
+
* @param {Object} token - The token object
|
|
23
|
+
* @returns {string|undefined} The token's $description property
|
|
24
|
+
*/
|
|
25
|
+
const getDtcgDescription = (token) => token.$description;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Checks if a token has a dark mode value
|
|
29
|
+
* @param {Object} token - The token object
|
|
30
|
+
* @returns {boolean} True if the token has a dark mode value defined
|
|
31
|
+
*/
|
|
32
|
+
const hasDarkValue = (token) =>
|
|
33
|
+
token.$extensions?.appearance?.dark?.$value !== undefined;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gets the dark mode value from a token
|
|
37
|
+
* @param {Object} token - The token object
|
|
38
|
+
* @returns {*} The dark mode value, or undefined if not present
|
|
39
|
+
*/
|
|
40
|
+
const getDarkValue = (token) => token.$extensions?.appearance?.dark?.$value;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a value is a composite color object (contains color and alpha properties)
|
|
44
|
+
* @param {*} value - The value to check
|
|
45
|
+
* @returns {boolean} True if the value is a composite color object
|
|
46
|
+
* @example
|
|
47
|
+
* isCompositeColor({ color: '#fff', alpha: 0.5 }) // true
|
|
48
|
+
* isCompositeColor('#fff') // false
|
|
49
|
+
*/
|
|
50
|
+
const isCompositeColor = (value) =>
|
|
51
|
+
value &&
|
|
52
|
+
typeof value === "object" &&
|
|
53
|
+
!Array.isArray(value) &&
|
|
54
|
+
value.color !== undefined &&
|
|
55
|
+
value.alpha !== undefined;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a value is a dimension object (contains value and unit properties)
|
|
59
|
+
* @param {*} value - The value to check
|
|
60
|
+
* @returns {boolean} True if the value is a dimension object
|
|
61
|
+
* @example
|
|
62
|
+
* isDimensionValue({ value: 16, unit: 'px' }) // true
|
|
63
|
+
* isDimensionValue('16px') // false
|
|
64
|
+
*/
|
|
65
|
+
const isDimensionValue = (value) =>
|
|
66
|
+
value &&
|
|
67
|
+
typeof value === "object" &&
|
|
68
|
+
!Array.isArray(value) &&
|
|
69
|
+
value.value !== undefined &&
|
|
70
|
+
value.unit !== undefined;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Checks if a value is a DTCG gradient object ({ type, angle, stops })
|
|
74
|
+
* @param {*} value - The value to check
|
|
75
|
+
* @returns {boolean} True if the value is a gradient object
|
|
76
|
+
* @example
|
|
77
|
+
* isGradientValue({ type: 'linear', angle: 90, stops: [...] }) // true
|
|
78
|
+
* isGradientValue('#fff') // false
|
|
79
|
+
*/
|
|
80
|
+
const isGradientValue = (value) =>
|
|
81
|
+
value &&
|
|
82
|
+
typeof value === "object" &&
|
|
83
|
+
!Array.isArray(value) &&
|
|
84
|
+
typeof value.type === "string" &&
|
|
85
|
+
typeof value.angle === "number" &&
|
|
86
|
+
Array.isArray(value.stops);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolves a gradient token's $value to the actual gradient object ({ type, angle, stops }),
|
|
90
|
+
* following reference strings (e.g. "{gradient.primary}") through the tokenMap if needed.
|
|
91
|
+
* @param {Object} token - The token object
|
|
92
|
+
* @param {Map<string, Object>} tokenMap - Map of token names to token objects
|
|
93
|
+
* @returns {Object|null} The resolved gradient value object, or null if not resolvable
|
|
94
|
+
*/
|
|
95
|
+
const resolveGradientValue = (token, tokenMap) => {
|
|
96
|
+
const originalValue = token.original?.$value;
|
|
97
|
+
if (isGradientValue(originalValue)) return originalValue;
|
|
98
|
+
if (typeof originalValue === "string" && originalValue.includes("{")) {
|
|
99
|
+
const refPath = originalValue.match(/\{([^{}]+)\}/)?.[1];
|
|
100
|
+
if (refPath) {
|
|
101
|
+
const refToken = tokenMap.get(refPath.replace(/\./g, "-"));
|
|
102
|
+
if (refToken) return resolveGradientValue(refToken, tokenMap);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Builds a CSS gradient string from a DTCG gradient value object, preserving CSS custom
|
|
110
|
+
* property references for each color stop. Uses the per-stop appearance extension to
|
|
111
|
+
* resolve light/dark color values.
|
|
112
|
+
* @param {Object} gradientValue - The gradient value object ({ type, angle, stops })
|
|
113
|
+
* @param {boolean} isDark - Whether to use dark mode stop colors
|
|
114
|
+
* @param {Map<string, Object>} tokenMap - Map of token names to token objects
|
|
115
|
+
* @param {Object} dictionary - The Style Dictionary dictionary object
|
|
116
|
+
* @param {string} cssVarPrefix - Prefix for CSS variable names (e.g., 'a2-')
|
|
117
|
+
* @param {Function} usesReferences - The SD5 usesReferences function
|
|
118
|
+
* @param {Function} resolveReferences - The SD5 resolveReferences function
|
|
119
|
+
* @returns {string} CSS gradient string with var() chains for stop colors
|
|
120
|
+
* @example
|
|
121
|
+
* buildGradientValue({ type: 'linear', angle: 90, stops }, false, tokenMap, dictionary, 'a2-', usesReferences, resolveReferences)
|
|
122
|
+
* // → 'linear-gradient(90deg, var(--a2-color-blue-600, #0265dc) 0%, var(--a2-color-cyan-300, #45d8f2) 100%)'
|
|
123
|
+
*/
|
|
124
|
+
const buildGradientValue = (
|
|
125
|
+
gradientValue,
|
|
126
|
+
isDark,
|
|
127
|
+
tokenMap,
|
|
128
|
+
dictionary,
|
|
129
|
+
cssVarPrefix,
|
|
130
|
+
usesReferences,
|
|
131
|
+
resolveReferences,
|
|
132
|
+
) => {
|
|
133
|
+
const { type, angle, stops } = gradientValue;
|
|
134
|
+
const stopStrings = stops.map((stop) => {
|
|
135
|
+
const pct =
|
|
136
|
+
typeof stop.position === "number"
|
|
137
|
+
? `${stop.position * 100}%`
|
|
138
|
+
: stop.position.replace(/\{([^{}]+)\}/g, (match) =>
|
|
139
|
+
buildRecursiveVarChainWithLightDark(
|
|
140
|
+
match,
|
|
141
|
+
tokenMap,
|
|
142
|
+
dictionary,
|
|
143
|
+
false,
|
|
144
|
+
usesReferences,
|
|
145
|
+
resolveReferences,
|
|
146
|
+
cssVarPrefix,
|
|
147
|
+
),
|
|
148
|
+
);
|
|
149
|
+
const colorRef = isDark
|
|
150
|
+
? (stop.color.$extensions?.appearance?.dark?.$value ?? stop.color.$value)
|
|
151
|
+
: (stop.color.$extensions?.appearance?.light?.$value ??
|
|
152
|
+
stop.color.$value);
|
|
153
|
+
if (typeof colorRef === "string" && usesReferences(colorRef)) {
|
|
154
|
+
const varChain = buildRecursiveVarChainWithLightDark(
|
|
155
|
+
colorRef,
|
|
156
|
+
tokenMap,
|
|
157
|
+
dictionary,
|
|
158
|
+
false,
|
|
159
|
+
usesReferences,
|
|
160
|
+
resolveReferences,
|
|
161
|
+
cssVarPrefix,
|
|
162
|
+
);
|
|
163
|
+
return `${varChain} ${pct}`;
|
|
164
|
+
}
|
|
165
|
+
return `${colorRef} ${pct}`;
|
|
166
|
+
});
|
|
167
|
+
return `${type}-gradient(${angle}deg, ${stopStrings.join(", ")})`;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Wraps values containing commas in double quotes for SCSS map compatibility.
|
|
172
|
+
* SCSS maps use commas as entry separators, so values with commas need quoting.
|
|
173
|
+
* @param {*} value - The value to potentially wrap
|
|
174
|
+
* @returns {*} The wrapped value if it contains commas, otherwise the original value
|
|
175
|
+
* @example
|
|
176
|
+
* wrapScssValue('Arial, sans-serif') // '"Arial, sans-serif"'
|
|
177
|
+
* wrapScssValue('Arial') // 'Arial'
|
|
178
|
+
*/
|
|
179
|
+
const wrapScssValue = (value) => {
|
|
180
|
+
if (typeof value !== "string") return value;
|
|
181
|
+
if (value.includes(",")) {
|
|
182
|
+
return `"${value}"`;
|
|
183
|
+
}
|
|
184
|
+
return value;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Gets the token name from its path, filtering out $schema and other $-prefixed segments
|
|
189
|
+
* @param {Object} token - The token object
|
|
190
|
+
* @returns {string} The token name as a hyphen-separated string
|
|
191
|
+
* @example
|
|
192
|
+
* getTokenName({ path: ['color', 'primary', '500'] }) // 'color-primary-500'
|
|
193
|
+
*/
|
|
194
|
+
const getTokenName = (token) => {
|
|
195
|
+
const tokenPath = token.path;
|
|
196
|
+
if (Array.isArray(tokenPath) && tokenPath.length > 0) {
|
|
197
|
+
const filteredPath = tokenPath.filter(
|
|
198
|
+
(segment) => segment !== "$schema" && !String(segment).startsWith("$"),
|
|
199
|
+
);
|
|
200
|
+
if (filteredPath.length > 0) {
|
|
201
|
+
return filteredPath.join("-");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return "unknown";
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Builds a Map of tokens keyed by their names for O(1) lookups.
|
|
209
|
+
* Result is memoized per dictionary instance to avoid redundant construction
|
|
210
|
+
* across multiple format calls that share the same dictionary.
|
|
211
|
+
* @param {Object} dictionary - The Style Dictionary dictionary object
|
|
212
|
+
* @returns {Map<string, Object>} A Map where keys are token names and values are token objects
|
|
213
|
+
*/
|
|
214
|
+
const _tokenMapCache = new WeakMap();
|
|
215
|
+
const buildTokenMap = (dictionary) => {
|
|
216
|
+
if (_tokenMapCache.has(dictionary)) return _tokenMapCache.get(dictionary);
|
|
217
|
+
const map = new Map(
|
|
218
|
+
(dictionary.unfilteredAllTokens || dictionary.allTokens).map((t) => [
|
|
219
|
+
getTokenName(t),
|
|
220
|
+
t,
|
|
221
|
+
]),
|
|
222
|
+
);
|
|
223
|
+
_tokenMapCache.set(dictionary, map);
|
|
224
|
+
return map;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Converts a hex color to hex8 format (with alpha channel)
|
|
229
|
+
* @param {string} hexColor - The hex color to convert (e.g., '#ff0000' or '#f00')
|
|
230
|
+
* @param {number} alpha - The alpha value between 0 and 1
|
|
231
|
+
* @returns {string} The hex8 color string (e.g., '#ff000080')
|
|
232
|
+
* @example
|
|
233
|
+
* convertToHex8('#ff0000', 0.5) // '#ff000080'
|
|
234
|
+
* convertToHex8('#f00', 1) // '#ff0000FF'
|
|
235
|
+
*/
|
|
236
|
+
const convertToHex8 = (hexColor, alpha) => {
|
|
237
|
+
if (!hexColor || typeof hexColor !== "string" || !hexColor.startsWith("#")) {
|
|
238
|
+
return hexColor;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Remove # and normalize to 6-digit hex
|
|
242
|
+
let hex = hexColor.replace("#", "");
|
|
243
|
+
if (hex.length === 3) {
|
|
244
|
+
// Expand shorthand: #RGB -> #RRGGBB
|
|
245
|
+
hex = hex
|
|
246
|
+
.split("")
|
|
247
|
+
.map((char) => char + char)
|
|
248
|
+
.join("");
|
|
249
|
+
} else if (hex.length !== 6) {
|
|
250
|
+
return hexColor; // Invalid hex format
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Convert alpha (0-1) to hex (00-FF)
|
|
254
|
+
const alphaHex = Math.round(alpha * 255)
|
|
255
|
+
.toString(16)
|
|
256
|
+
.padStart(2, "0")
|
|
257
|
+
.toUpperCase();
|
|
258
|
+
|
|
259
|
+
return `#${hex}${alphaHex}`;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Converts a composite color { color, alpha } to a CSS color-mix() expression,
|
|
264
|
+
* preserving the primitive CSS custom property reference.
|
|
265
|
+
*
|
|
266
|
+
* Example output:
|
|
267
|
+
* color-mix(in srgb, var(--a2-color-neutral-0, #ffffff) 8%, transparent)
|
|
268
|
+
*
|
|
269
|
+
* This keeps the live link to the primitive token so runtime theme overrides
|
|
270
|
+
* (e.g. changing --a2-color-neutral-0) automatically update the alpha color too.
|
|
271
|
+
*
|
|
272
|
+
* Browser support: Chrome 111+, Firefox 113+, Safari 16.2+ (as of 2024).
|
|
273
|
+
*
|
|
274
|
+
* ---
|
|
275
|
+
* FUTURE: CSS Relative Color Syntax (Option 3)
|
|
276
|
+
* When browser support matures (Chrome 119+, Safari 17.5+, Firefox 128+),
|
|
277
|
+
* this can be simplified to:
|
|
278
|
+
* rgb(from var(--a2-color-neutral-0, #ffffff) r g b / 0.08)
|
|
279
|
+
* Change the return statement below to that template and remove the alpha→percentage
|
|
280
|
+
* conversion. The `from` keyword extracts r/g/b channels from the referenced color,
|
|
281
|
+
* so no color-space conversion is needed. Switch when your minimum browser targets
|
|
282
|
+
* include the versions listed above.
|
|
283
|
+
* ---
|
|
284
|
+
*
|
|
285
|
+
* @param {string} refTokenName - The token name (hyphen-separated, e.g. 'color-neutral-0')
|
|
286
|
+
* @param {string} hexFallback - The resolved hex color as a fallback (e.g. '#ffffff')
|
|
287
|
+
* @param {number} alpha - The alpha value between 0 and 1
|
|
288
|
+
* @param {string} [cssVarPrefix=''] - Prefix for CSS variable names (e.g. 'a2-')
|
|
289
|
+
* @returns {string} CSS color-mix() expression
|
|
290
|
+
* @example
|
|
291
|
+
* compositeColorToCssMix('color-neutral-0', '#ffffff', 0.08, 'a2-')
|
|
292
|
+
* // → 'color-mix(in srgb, var(--a2-color-neutral-0, #ffffff) 8%, transparent)'
|
|
293
|
+
*/
|
|
294
|
+
const compositeColorToCssMix = (
|
|
295
|
+
refTokenName,
|
|
296
|
+
hexFallback,
|
|
297
|
+
alpha,
|
|
298
|
+
cssVarPrefix = "",
|
|
299
|
+
) => {
|
|
300
|
+
const pct = Math.round(alpha * 10000) / 100; // avoid floating point drift, e.g. 0.06 → 6
|
|
301
|
+
return `color-mix(in srgb, var(--${cssVarPrefix}${refTokenName}, ${hexFallback}) ${pct}%, transparent)`;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Resolves a reference string using Style Dictionary utilities.
|
|
306
|
+
* @param {string} refString - The reference string to resolve (e.g., '{color.primary.500}')
|
|
307
|
+
* @param {Object} dictionary - The Style Dictionary dictionary object
|
|
308
|
+
* @param {Function} resolveReferences - The SD5 resolveReferences function
|
|
309
|
+
* @returns {*} The resolved value, or the original string if resolution fails
|
|
310
|
+
*/
|
|
311
|
+
const resolveReference = (refString, dictionary, resolveReferences) => {
|
|
312
|
+
if (typeof refString !== "string") {
|
|
313
|
+
return refString;
|
|
314
|
+
}
|
|
315
|
+
// Use unfiltered tokens when available so references to theme/primitive tokens
|
|
316
|
+
// resolve correctly in filtered builds (e.g. component.js only has component tokens).
|
|
317
|
+
const tokensForResolution = dictionary.unfilteredTokens ?? dictionary.tokens;
|
|
318
|
+
try {
|
|
319
|
+
return resolveReferences(refString, tokensForResolution, {
|
|
320
|
+
usesDtcg: true,
|
|
321
|
+
});
|
|
322
|
+
} catch {
|
|
323
|
+
return refString;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Resolves a composite color object (with color and alpha properties) to a CSS value.
|
|
329
|
+
*
|
|
330
|
+
* When a tokenMap and cssVarPrefix are provided and the color references a known primitive
|
|
331
|
+
* token, emits a color-mix() expression that preserves the CSS custom property reference:
|
|
332
|
+
* color-mix(in srgb, var(--a2-color-neutral-0, #ffffff) 8%, transparent)
|
|
333
|
+
*
|
|
334
|
+
* Falls back to a hex8 string (#rrggbbAA) when the primitive token is not in tokenMap
|
|
335
|
+
* or when tokenMap is not provided (e.g. in SCSS map / JS export contexts where a
|
|
336
|
+
* static value is needed).
|
|
337
|
+
*
|
|
338
|
+
* @param {Object} colorObj - The composite color object with color and alpha properties
|
|
339
|
+
* @param {Object} dictionary - The Style Dictionary dictionary object
|
|
340
|
+
* @param {Function} resolveReferences - The SD5 resolveReferences function
|
|
341
|
+
* @param {Map<string, Object>} [tokenMap] - Optional token map for CSS var name lookup
|
|
342
|
+
* @param {string} [cssVarPrefix=''] - Optional prefix for CSS variable names (e.g. 'a2-')
|
|
343
|
+
* @returns {string|null} CSS color value string, or null if not a composite color
|
|
344
|
+
*/
|
|
345
|
+
const resolveCompositeColor = (
|
|
346
|
+
colorObj,
|
|
347
|
+
dictionary,
|
|
348
|
+
resolveReferences,
|
|
349
|
+
tokenMap,
|
|
350
|
+
cssVarPrefix = "",
|
|
351
|
+
) => {
|
|
352
|
+
if (!isCompositeColor(colorObj)) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const colorValue = resolveReference(
|
|
356
|
+
colorObj.color,
|
|
357
|
+
dictionary,
|
|
358
|
+
resolveReferences,
|
|
359
|
+
);
|
|
360
|
+
if (typeof colorValue === "string" && colorValue.startsWith("#")) {
|
|
361
|
+
// When a tokenMap is available, emit color-mix() to preserve the primitive var reference.
|
|
362
|
+
if (tokenMap && typeof colorObj.color === "string") {
|
|
363
|
+
const refMatch = colorObj.color.match(/\{([^{}]+)\}/);
|
|
364
|
+
if (refMatch) {
|
|
365
|
+
const refTokenName = refMatch[1].replace(/\./g, "-");
|
|
366
|
+
if (tokenMap.has(refTokenName)) {
|
|
367
|
+
return compositeColorToCssMix(
|
|
368
|
+
refTokenName,
|
|
369
|
+
colorValue,
|
|
370
|
+
colorObj.alpha,
|
|
371
|
+
cssVarPrefix,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return convertToHex8(colorValue, colorObj.alpha);
|
|
377
|
+
}
|
|
378
|
+
return colorValue;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Gets the resolved token value, handling dark mode, composite colors, and references
|
|
383
|
+
* @param {Object} token - The token object
|
|
384
|
+
* @param {Object} dictionary - The Style Dictionary dictionary object
|
|
385
|
+
* @param {Object} [options] - Options for value resolution
|
|
386
|
+
* @param {boolean} [options.isDark=false] - Whether to get the dark mode value
|
|
387
|
+
* @param {boolean} [options.forScssMap=false] - Whether to format for SCSS map (quotes commas)
|
|
388
|
+
* @param {Function} resolveReferences - The SD5 resolveReferences function
|
|
389
|
+
* @returns {string} The resolved token value as a string
|
|
390
|
+
*/
|
|
391
|
+
const getTokenValue = (
|
|
392
|
+
token,
|
|
393
|
+
dictionary,
|
|
394
|
+
{ isDark = false, forScssMap = false } = {},
|
|
395
|
+
resolveReferences,
|
|
396
|
+
) => {
|
|
397
|
+
let value = getDtcgValue(token);
|
|
398
|
+
|
|
399
|
+
// Handle dark mode
|
|
400
|
+
if (isDark && hasDarkValue(token)) {
|
|
401
|
+
value = getDarkValue(token);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Handle composite colors
|
|
405
|
+
if (isCompositeColor(value)) {
|
|
406
|
+
const resolved = resolveCompositeColor(
|
|
407
|
+
value,
|
|
408
|
+
dictionary,
|
|
409
|
+
resolveReferences,
|
|
410
|
+
);
|
|
411
|
+
if (resolved) {
|
|
412
|
+
value = resolved;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Handle dimension values { value, unit }
|
|
417
|
+
if (isDimensionValue(value)) {
|
|
418
|
+
value = `${value.value}${value.unit}`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Resolve references; SD5 resolveReferences may return a token object, so extract value and loop until primitive
|
|
422
|
+
for (;;) {
|
|
423
|
+
if (
|
|
424
|
+
typeof value === "string" &&
|
|
425
|
+
value.includes("{") &&
|
|
426
|
+
value.includes("}")
|
|
427
|
+
) {
|
|
428
|
+
value = resolveReference(value, dictionary, resolveReferences);
|
|
429
|
+
}
|
|
430
|
+
// SD5 can return the full token object; extract value
|
|
431
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
432
|
+
if (value.$value !== undefined || value.value !== undefined) {
|
|
433
|
+
value =
|
|
434
|
+
isDark && value.$extensions?.appearance?.dark?.$value !== undefined
|
|
435
|
+
? value.$extensions.appearance.dark.$value
|
|
436
|
+
: (value.$value ?? value.value);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (isCompositeColor(value)) {
|
|
441
|
+
const resolved = resolveCompositeColor(
|
|
442
|
+
value,
|
|
443
|
+
dictionary,
|
|
444
|
+
resolveReferences,
|
|
445
|
+
);
|
|
446
|
+
if (resolved) {
|
|
447
|
+
value = resolved;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (isDimensionValue(value)) {
|
|
451
|
+
value = `${value.value}${value.unit}`;
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Convert to string and clean up
|
|
457
|
+
if (value === undefined || value === null) {
|
|
458
|
+
return "";
|
|
459
|
+
}
|
|
460
|
+
const valueStr = typeof value === "string" ? value : JSON.stringify(value);
|
|
461
|
+
|
|
462
|
+
// Normalize transparent to rgba for consistency
|
|
463
|
+
const cleaned = valueStr.replaceAll('"', "");
|
|
464
|
+
if (cleaned === "transparent") {
|
|
465
|
+
return "rgba(0, 0, 0, 0)";
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Wrap values with commas in quotes for SCSS map compatibility
|
|
469
|
+
if (forScssMap) {
|
|
470
|
+
return wrapScssValue(cleaned);
|
|
471
|
+
}
|
|
472
|
+
return cleaned;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Checks if a value is a reference string using the usesReferences function
|
|
477
|
+
* @param {*} value - The value to check
|
|
478
|
+
* @param {Function} usesReferences - The SD5 usesReferences function
|
|
479
|
+
* @returns {boolean} True if the value is a reference string
|
|
480
|
+
*/
|
|
481
|
+
const isReferenceString = (value, usesReferences) =>
|
|
482
|
+
typeof value === "string" && usesReferences(value);
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Builds a recursive CSS var chain with light-dark() at the level where light/dark values differ.
|
|
486
|
+
* Creates nested var() fallbacks that ultimately resolve to light-dark() at the primitive level.
|
|
487
|
+
* @param {string} originalValue - The original reference string (e.g., '{status.color.danger}')
|
|
488
|
+
* @param {Map<string, Object>} tokenMap - Map of token names to token objects
|
|
489
|
+
* @param {Object} dictionary - The Style Dictionary dictionary object
|
|
490
|
+
* @param {boolean} [useLightDark=false] - Whether to include light-dark() for color variants
|
|
491
|
+
* @param {Function} usesReferences - The SD5 usesReferences function
|
|
492
|
+
* @param {Function} resolveReferences - The SD5 resolveReferences function
|
|
493
|
+
* @param {string} [cssVarPrefix=''] - Prefix for CSS variable names (e.g., 'a2-')
|
|
494
|
+
* @param {boolean} [forScssMap=false] - Whether to format for SCSS map
|
|
495
|
+
* @returns {string} The CSS var chain string
|
|
496
|
+
* @example
|
|
497
|
+
* // Returns: 'var(--a2-status-color-danger, light-dark(var(--a2-color-red-500, #e13212), var(--a2-color-red-100, #ff745f)))'
|
|
498
|
+
* buildRecursiveVarChainWithLightDark('{status.color.danger}', tokenMap, dictionary, true, usesReferences, resolveReferences, 'a2-')
|
|
499
|
+
*/
|
|
500
|
+
const buildRecursiveVarChainWithLightDark = (
|
|
501
|
+
originalValue,
|
|
502
|
+
tokenMap,
|
|
503
|
+
dictionary,
|
|
504
|
+
useLightDark = false,
|
|
505
|
+
usesReferences,
|
|
506
|
+
resolveReferences,
|
|
507
|
+
cssVarPrefix = "",
|
|
508
|
+
forScssMap = false,
|
|
509
|
+
) => {
|
|
510
|
+
// If not a reference string, return the value as-is
|
|
511
|
+
if (
|
|
512
|
+
!originalValue ||
|
|
513
|
+
typeof originalValue !== "string" ||
|
|
514
|
+
!usesReferences(originalValue)
|
|
515
|
+
) {
|
|
516
|
+
return originalValue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Replace each {reference} with var(--name, recursiveResult)
|
|
520
|
+
return originalValue.replace(/\{([^{}]+)\}/g, (_, refPath) => {
|
|
521
|
+
const refTokenName = refPath.replace(/\./g, "-");
|
|
522
|
+
const refToken = tokenMap.get(refTokenName);
|
|
523
|
+
if (!refToken) return `var(--${cssVarPrefix}${refTokenName})`;
|
|
524
|
+
|
|
525
|
+
const refOriginalLight = refToken.original?.$value;
|
|
526
|
+
const refOriginalDark =
|
|
527
|
+
refToken.original?.$extensions?.appearance?.dark?.$value;
|
|
528
|
+
const refHasDarkVariant = refOriginalDark !== undefined;
|
|
529
|
+
|
|
530
|
+
// Check if light and dark values are DIFFERENT - this is where light-dark() should go
|
|
531
|
+
const lightDarkDiffer =
|
|
532
|
+
refHasDarkVariant && refOriginalLight !== refOriginalDark;
|
|
533
|
+
|
|
534
|
+
if (useLightDark && lightDarkDiffer) {
|
|
535
|
+
// Light and dark values differ - insert light-dark() here
|
|
536
|
+
const lightResult = buildRecursiveVarChainWithLightDark(
|
|
537
|
+
refOriginalLight,
|
|
538
|
+
tokenMap,
|
|
539
|
+
dictionary,
|
|
540
|
+
false,
|
|
541
|
+
usesReferences,
|
|
542
|
+
resolveReferences,
|
|
543
|
+
cssVarPrefix,
|
|
544
|
+
forScssMap,
|
|
545
|
+
);
|
|
546
|
+
// Same fix as darkFinal: ensure lightResult is a string before using it
|
|
547
|
+
const lightFinal =
|
|
548
|
+
typeof lightResult === "string" && usesReferences(refOriginalLight)
|
|
549
|
+
? lightResult
|
|
550
|
+
: getTokenValue(
|
|
551
|
+
refToken,
|
|
552
|
+
dictionary,
|
|
553
|
+
{ isDark: false, forScssMap },
|
|
554
|
+
resolveReferences,
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
const darkResult = buildRecursiveVarChainWithLightDark(
|
|
558
|
+
refOriginalDark,
|
|
559
|
+
tokenMap,
|
|
560
|
+
dictionary,
|
|
561
|
+
false,
|
|
562
|
+
usesReferences,
|
|
563
|
+
resolveReferences,
|
|
564
|
+
cssVarPrefix,
|
|
565
|
+
forScssMap,
|
|
566
|
+
);
|
|
567
|
+
// Ensure darkResult is a string (resolved var chain), not just truthy.
|
|
568
|
+
// Composite color objects pass through buildRecursiveVarChainWithLightDark unchanged,
|
|
569
|
+
// but usesReferences() returns true for them (checks nested properties).
|
|
570
|
+
const darkFinal =
|
|
571
|
+
typeof darkResult === "string" && usesReferences(refOriginalDark)
|
|
572
|
+
? darkResult
|
|
573
|
+
: getTokenValue(
|
|
574
|
+
refToken,
|
|
575
|
+
dictionary,
|
|
576
|
+
{ isDark: true, forScssMap },
|
|
577
|
+
resolveReferences,
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
return `var(--${cssVarPrefix}${refTokenName}, light-dark(${lightFinal}, ${darkFinal}))`;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Light and dark are same (or no dark) - continue recursing, propagate useLightDark
|
|
584
|
+
if (
|
|
585
|
+
refOriginalLight &&
|
|
586
|
+
typeof refOriginalLight === "string" &&
|
|
587
|
+
usesReferences(refOriginalLight)
|
|
588
|
+
) {
|
|
589
|
+
const nestedResult = buildRecursiveVarChainWithLightDark(
|
|
590
|
+
refOriginalLight,
|
|
591
|
+
tokenMap,
|
|
592
|
+
dictionary,
|
|
593
|
+
useLightDark,
|
|
594
|
+
usesReferences,
|
|
595
|
+
resolveReferences,
|
|
596
|
+
cssVarPrefix,
|
|
597
|
+
forScssMap,
|
|
598
|
+
);
|
|
599
|
+
return `var(--${cssVarPrefix}${refTokenName}, ${nestedResult})`;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Final value (primitive) - get resolved value
|
|
603
|
+
const finalValue = getTokenValue(
|
|
604
|
+
refToken,
|
|
605
|
+
dictionary,
|
|
606
|
+
{ isDark: false, forScssMap },
|
|
607
|
+
resolveReferences,
|
|
608
|
+
);
|
|
609
|
+
return `var(--${cssVarPrefix}${refTokenName}, ${finalValue})`;
|
|
610
|
+
});
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Builds a fallback value with recursive refs and light-dark() at the appropriate level.
|
|
615
|
+
* This is the main entry point for generating CSS fallback values with dark mode support.
|
|
616
|
+
* @param {Object} token - The token object
|
|
617
|
+
* @param {Object} dictionary - The Style Dictionary dictionary object
|
|
618
|
+
* @param {Map<string, Object>} tokenMap - Map of token names to token objects
|
|
619
|
+
* @param {Object} [options] - Options for fallback building
|
|
620
|
+
* @param {boolean} [options.isDark=false] - Whether to get the dark mode value
|
|
621
|
+
* @param {boolean} [options.useLightDark=false] - Whether to include light-dark() for color variants
|
|
622
|
+
* @param {string} [options.cssVarPrefix=''] - Prefix for CSS variable names
|
|
623
|
+
* @param {boolean} [options.forScssMap=false] - Whether to format for SCSS map
|
|
624
|
+
* @param {Function} usesReferences - The SD5 usesReferences function
|
|
625
|
+
* @param {Function} resolveReferences - The SD5 resolveReferences function
|
|
626
|
+
* @returns {string} The fallback value string with CSS var chains and/or light-dark()
|
|
627
|
+
*/
|
|
628
|
+
const buildFallbackWithRefs = (
|
|
629
|
+
token,
|
|
630
|
+
dictionary,
|
|
631
|
+
tokenMap,
|
|
632
|
+
{
|
|
633
|
+
isDark = false,
|
|
634
|
+
useLightDark = false,
|
|
635
|
+
cssVarPrefix = "",
|
|
636
|
+
forScssMap = false,
|
|
637
|
+
} = {},
|
|
638
|
+
usesReferences,
|
|
639
|
+
resolveReferences,
|
|
640
|
+
) => {
|
|
641
|
+
const originalValue = token.original?.$value;
|
|
642
|
+
const originalDark = token.original?.$extensions?.appearance?.dark?.$value;
|
|
643
|
+
const hasDark = originalDark !== undefined;
|
|
644
|
+
|
|
645
|
+
// Check if this token's light/dark values are different at top level
|
|
646
|
+
// Use JSON.stringify for object comparison (composite colors)
|
|
647
|
+
const lightDarkDifferAtTop =
|
|
648
|
+
hasDark && JSON.stringify(originalValue) !== JSON.stringify(originalDark);
|
|
649
|
+
|
|
650
|
+
// If useLightDark and light/dark differ at this level, insert light-dark() here
|
|
651
|
+
if (useLightDark && lightDarkDifferAtTop) {
|
|
652
|
+
// Handle light value - could be a string reference or composite object
|
|
653
|
+
let lightFinal;
|
|
654
|
+
if (isReferenceString(originalValue, usesReferences)) {
|
|
655
|
+
lightFinal = buildRecursiveVarChainWithLightDark(
|
|
656
|
+
originalValue,
|
|
657
|
+
tokenMap,
|
|
658
|
+
dictionary,
|
|
659
|
+
false,
|
|
660
|
+
usesReferences,
|
|
661
|
+
resolveReferences,
|
|
662
|
+
cssVarPrefix,
|
|
663
|
+
forScssMap,
|
|
664
|
+
);
|
|
665
|
+
} else {
|
|
666
|
+
// Composite color or plain value - resolve directly.
|
|
667
|
+
// Pass tokenMap + cssVarPrefix so composite colors emit color-mix() in CSS contexts.
|
|
668
|
+
if (isCompositeColor(originalValue)) {
|
|
669
|
+
lightFinal =
|
|
670
|
+
resolveCompositeColor(
|
|
671
|
+
originalValue,
|
|
672
|
+
dictionary,
|
|
673
|
+
resolveReferences,
|
|
674
|
+
tokenMap,
|
|
675
|
+
cssVarPrefix,
|
|
676
|
+
) ??
|
|
677
|
+
getTokenValue(
|
|
678
|
+
token,
|
|
679
|
+
dictionary,
|
|
680
|
+
{ isDark: false, forScssMap },
|
|
681
|
+
resolveReferences,
|
|
682
|
+
);
|
|
683
|
+
} else {
|
|
684
|
+
lightFinal = getTokenValue(
|
|
685
|
+
token,
|
|
686
|
+
dictionary,
|
|
687
|
+
{ isDark: false, forScssMap },
|
|
688
|
+
resolveReferences,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Handle dark value - could be a string reference or composite object
|
|
694
|
+
let darkFinal;
|
|
695
|
+
if (isReferenceString(originalDark, usesReferences)) {
|
|
696
|
+
darkFinal = buildRecursiveVarChainWithLightDark(
|
|
697
|
+
originalDark,
|
|
698
|
+
tokenMap,
|
|
699
|
+
dictionary,
|
|
700
|
+
false,
|
|
701
|
+
usesReferences,
|
|
702
|
+
resolveReferences,
|
|
703
|
+
cssVarPrefix,
|
|
704
|
+
forScssMap,
|
|
705
|
+
);
|
|
706
|
+
} else {
|
|
707
|
+
// Composite color or plain value - resolve directly.
|
|
708
|
+
// Pass tokenMap + cssVarPrefix so composite colors emit color-mix() in CSS contexts.
|
|
709
|
+
if (isCompositeColor(originalDark)) {
|
|
710
|
+
darkFinal =
|
|
711
|
+
resolveCompositeColor(
|
|
712
|
+
originalDark,
|
|
713
|
+
dictionary,
|
|
714
|
+
resolveReferences,
|
|
715
|
+
tokenMap,
|
|
716
|
+
cssVarPrefix,
|
|
717
|
+
) ??
|
|
718
|
+
getTokenValue(
|
|
719
|
+
token,
|
|
720
|
+
dictionary,
|
|
721
|
+
{ isDark: true, forScssMap },
|
|
722
|
+
resolveReferences,
|
|
723
|
+
);
|
|
724
|
+
} else {
|
|
725
|
+
darkFinal = getTokenValue(
|
|
726
|
+
token,
|
|
727
|
+
dictionary,
|
|
728
|
+
{ isDark: true, forScssMap },
|
|
729
|
+
resolveReferences,
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return `light-dark(${lightFinal}, ${darkFinal})`;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Light/dark same or no dark - recurse and propagate useLightDark to find where they differ
|
|
738
|
+
// When isDark is true and we have a dark value, use originalDark instead of originalValue
|
|
739
|
+
const valueToProcess = isDark && hasDark ? originalDark : originalValue;
|
|
740
|
+
|
|
741
|
+
if (isReferenceString(valueToProcess, usesReferences)) {
|
|
742
|
+
const result = buildRecursiveVarChainWithLightDark(
|
|
743
|
+
valueToProcess,
|
|
744
|
+
tokenMap,
|
|
745
|
+
dictionary,
|
|
746
|
+
useLightDark,
|
|
747
|
+
usesReferences,
|
|
748
|
+
resolveReferences,
|
|
749
|
+
cssVarPrefix,
|
|
750
|
+
forScssMap,
|
|
751
|
+
);
|
|
752
|
+
if (result && result !== valueToProcess) {
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Composite color with inner reference and same light/dark value: resolve referenced token
|
|
758
|
+
// to get a light-dark() pair. Uses color-mix() to preserve the primitive CSS var reference.
|
|
759
|
+
if (
|
|
760
|
+
useLightDark &&
|
|
761
|
+
isCompositeColor(originalValue) &&
|
|
762
|
+
isReferenceString(originalValue.color, usesReferences)
|
|
763
|
+
) {
|
|
764
|
+
const refMatch = originalValue.color.match(/\{([^{}]+)\}/);
|
|
765
|
+
if (refMatch) {
|
|
766
|
+
const refTokenName = refMatch[1].replace(/\./g, "-");
|
|
767
|
+
const refToken = tokenMap.get(refTokenName);
|
|
768
|
+
if (refToken && hasDarkValue(refToken)) {
|
|
769
|
+
const lightResolved = getTokenValue(
|
|
770
|
+
refToken,
|
|
771
|
+
dictionary,
|
|
772
|
+
{ isDark: false, forScssMap },
|
|
773
|
+
resolveReferences,
|
|
774
|
+
);
|
|
775
|
+
const darkResolved = getTokenValue(
|
|
776
|
+
refToken,
|
|
777
|
+
dictionary,
|
|
778
|
+
{ isDark: true, forScssMap },
|
|
779
|
+
resolveReferences,
|
|
780
|
+
);
|
|
781
|
+
if (
|
|
782
|
+
typeof lightResolved === "string" &&
|
|
783
|
+
typeof darkResolved === "string" &&
|
|
784
|
+
lightResolved.startsWith("#")
|
|
785
|
+
) {
|
|
786
|
+
const lightMix = compositeColorToCssMix(
|
|
787
|
+
refTokenName,
|
|
788
|
+
lightResolved,
|
|
789
|
+
originalValue.alpha,
|
|
790
|
+
cssVarPrefix,
|
|
791
|
+
);
|
|
792
|
+
const darkMix = compositeColorToCssMix(
|
|
793
|
+
refTokenName,
|
|
794
|
+
darkResolved,
|
|
795
|
+
originalValue.alpha,
|
|
796
|
+
cssVarPrefix,
|
|
797
|
+
);
|
|
798
|
+
return `light-dark(${lightMix}, ${darkMix})`;
|
|
799
|
+
}
|
|
800
|
+
return `light-dark(${lightResolved}, ${darkResolved})`;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// No references or composite value - return the resolved final value.
|
|
806
|
+
// For composite colors, pass tokenMap + cssVarPrefix to emit color-mix() in CSS contexts.
|
|
807
|
+
const valueToCheck = isDark && hasDark ? originalDark : originalValue;
|
|
808
|
+
if (isCompositeColor(valueToCheck)) {
|
|
809
|
+
const mixed = resolveCompositeColor(
|
|
810
|
+
valueToCheck,
|
|
811
|
+
dictionary,
|
|
812
|
+
resolveReferences,
|
|
813
|
+
tokenMap,
|
|
814
|
+
cssVarPrefix,
|
|
815
|
+
);
|
|
816
|
+
if (mixed) return mixed;
|
|
817
|
+
}
|
|
818
|
+
return getTokenValue(
|
|
819
|
+
token,
|
|
820
|
+
dictionary,
|
|
821
|
+
{ isDark, forScssMap },
|
|
822
|
+
resolveReferences,
|
|
823
|
+
);
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
module.exports = {
|
|
827
|
+
getDtcgValue,
|
|
828
|
+
getTokenType,
|
|
829
|
+
getDtcgDescription,
|
|
830
|
+
hasDarkValue,
|
|
831
|
+
getDarkValue,
|
|
832
|
+
isCompositeColor,
|
|
833
|
+
isDimensionValue,
|
|
834
|
+
isGradientValue,
|
|
835
|
+
wrapScssValue,
|
|
836
|
+
getTokenName,
|
|
837
|
+
buildTokenMap,
|
|
838
|
+
convertToHex8,
|
|
839
|
+
compositeColorToCssMix,
|
|
840
|
+
resolveReference,
|
|
841
|
+
resolveCompositeColor,
|
|
842
|
+
getTokenValue,
|
|
843
|
+
isReferenceString,
|
|
844
|
+
buildRecursiveVarChainWithLightDark,
|
|
845
|
+
buildFallbackWithRefs,
|
|
846
|
+
buildGradientValue,
|
|
847
|
+
resolveGradientValue,
|
|
848
|
+
};
|