@design-token-kit/core 0.1.1
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/LICENSE +159 -0
- package/README.md +57 -0
- package/lib/index.d.ts +608 -0
- package/lib/index.js +4737 -0
- package/lib/schemas/2025.10/format/group.json +55 -0
- package/lib/schemas/2025.10/format/groupOrToken.json +15 -0
- package/lib/schemas/2025.10/format/token.json +425 -0
- package/lib/schemas/2025.10/format/tokenType.json +23 -0
- package/lib/schemas/2025.10/format/values/border.json +45 -0
- package/lib/schemas/2025.10/format/values/color.json +515 -0
- package/lib/schemas/2025.10/format/values/cubicBezier.json +57 -0
- package/lib/schemas/2025.10/format/values/dimension.json +35 -0
- package/lib/schemas/2025.10/format/values/duration.json +35 -0
- package/lib/schemas/2025.10/format/values/fontFamily.json +34 -0
- package/lib/schemas/2025.10/format/values/fontWeight.json +39 -0
- package/lib/schemas/2025.10/format/values/gradient.json +53 -0
- package/lib/schemas/2025.10/format/values/number.json +8 -0
- package/lib/schemas/2025.10/format/values/shadow.json +103 -0
- package/lib/schemas/2025.10/format/values/strokeStyle.json +64 -0
- package/lib/schemas/2025.10/format/values/transition.json +45 -0
- package/lib/schemas/2025.10/format/values/typography.json +73 -0
- package/lib/schemas/2025.10/format.json +106 -0
- package/lib/schemas/2025.10/resolver/modifier.json +85 -0
- package/lib/schemas/2025.10/resolver/resolutionOrder.json +117 -0
- package/lib/schemas/2025.10/resolver/set.json +71 -0
- package/lib/schemas/2025.10/resolver.json +62 -0
- package/lib/schemas/hrdt-tokens.json +533 -0
- package/package.json +60 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,4737 @@
|
|
|
1
|
+
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import Ajv from "ajv";
|
|
7
|
+
import addFormats from "ajv-formats";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
//#region src/core/model/TokenGroup.ts
|
|
10
|
+
/**
|
|
11
|
+
* A named container for tokens and nested groups.
|
|
12
|
+
*
|
|
13
|
+
* Groups may declare a `$type` that is inherited by all descendant tokens
|
|
14
|
+
* unless overridden at a lower level. Groups may also extend another group
|
|
15
|
+
* via `$extends`.
|
|
16
|
+
*
|
|
17
|
+
* @see https://tr.designtokens.org/format/#groups
|
|
18
|
+
*/
|
|
19
|
+
var TokenGroup = class {
|
|
20
|
+
#children;
|
|
21
|
+
/** Inherited token type for descendant tokens that do not declare their own `$type`. */
|
|
22
|
+
type;
|
|
23
|
+
description;
|
|
24
|
+
deprecated;
|
|
25
|
+
extensions;
|
|
26
|
+
/** Reference to the group this group extends, if any. */
|
|
27
|
+
extends;
|
|
28
|
+
/**
|
|
29
|
+
* Optional root token for this group. Provides a base value while
|
|
30
|
+
* allowing for variants via nested tokens.
|
|
31
|
+
*
|
|
32
|
+
* @see https://tr.designtokens.org/format/#groups
|
|
33
|
+
*/
|
|
34
|
+
root;
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.type = options.type;
|
|
37
|
+
this.description = options.description;
|
|
38
|
+
this.deprecated = options.deprecated;
|
|
39
|
+
this.extensions = options.extensions;
|
|
40
|
+
this.extends = options.extends;
|
|
41
|
+
this.root = options.root;
|
|
42
|
+
this.#children = options.children ?? /* @__PURE__ */ new Map();
|
|
43
|
+
}
|
|
44
|
+
/** Returns the child token or group with the given name, or undefined. */
|
|
45
|
+
get(name) {
|
|
46
|
+
return this.#children.get(name);
|
|
47
|
+
}
|
|
48
|
+
/** Returns all child names in insertion order. */
|
|
49
|
+
keys() {
|
|
50
|
+
return this.#children.keys();
|
|
51
|
+
}
|
|
52
|
+
/** Returns all child entries as [name, node] pairs in insertion order. */
|
|
53
|
+
entries() {
|
|
54
|
+
return this.#children.entries();
|
|
55
|
+
}
|
|
56
|
+
/** Returns true if this group contains a child with the given name. */
|
|
57
|
+
has(name) {
|
|
58
|
+
return this.#children.has(name);
|
|
59
|
+
}
|
|
60
|
+
get size() {
|
|
61
|
+
return this.#children.size;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/core/model/TokenReference.ts
|
|
66
|
+
/**
|
|
67
|
+
* A reference to another token using curly-brace notation, e.g. `{color.base.red}`.
|
|
68
|
+
*
|
|
69
|
+
* References are not resolved at model level - resolution is the responsibility
|
|
70
|
+
* of the consumer (resolver, converter, etc.).
|
|
71
|
+
*
|
|
72
|
+
* @see https://tr.designtokens.org/format/#aliases-references
|
|
73
|
+
*/
|
|
74
|
+
var TokenReference = class {
|
|
75
|
+
#value;
|
|
76
|
+
/** @param value - token path without curly braces, e.g. `color.base.red` */
|
|
77
|
+
constructor(value) {
|
|
78
|
+
this.#value = value;
|
|
79
|
+
}
|
|
80
|
+
/** Token path without curly braces, e.g. `color.base.red`. */
|
|
81
|
+
get value() {
|
|
82
|
+
return this.#value;
|
|
83
|
+
}
|
|
84
|
+
/** Serializes to canonical curly-brace notation, e.g. `{color.base.red}`. */
|
|
85
|
+
toString() {
|
|
86
|
+
return `{${this.#value}}`;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/core/model/TokenNode.ts
|
|
91
|
+
/**
|
|
92
|
+
* Base class for all DTCG token nodes.
|
|
93
|
+
*
|
|
94
|
+
* A token node holds either a direct value (`$value`) or an alias to another token (`$ref`).
|
|
95
|
+
* The `$type` field is inherited from the containing group when not explicitly set.
|
|
96
|
+
* Alias tokens without an explicit or inherited `$type` have `type = undefined`.
|
|
97
|
+
*
|
|
98
|
+
* @see https://tr.designtokens.org/format/#design-token
|
|
99
|
+
*/
|
|
100
|
+
var TokenNode = class {
|
|
101
|
+
#type;
|
|
102
|
+
#value;
|
|
103
|
+
description;
|
|
104
|
+
deprecated;
|
|
105
|
+
extensions;
|
|
106
|
+
constructor(type, value, description, deprecated, extensions) {
|
|
107
|
+
this.#type = type;
|
|
108
|
+
this.#value = value;
|
|
109
|
+
this.description = description;
|
|
110
|
+
this.deprecated = deprecated;
|
|
111
|
+
this.extensions = extensions;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The token's DTCG type, or `undefined` for alias tokens without an explicit
|
|
115
|
+
* or inherited `$type`.
|
|
116
|
+
*/
|
|
117
|
+
get type() {
|
|
118
|
+
return this.#type;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The token's value, or a {@link TokenReference} when this token is an alias.
|
|
122
|
+
*
|
|
123
|
+
* @see https://tr.designtokens.org/format/#aliases-references
|
|
124
|
+
*/
|
|
125
|
+
get value() {
|
|
126
|
+
return this.#value;
|
|
127
|
+
}
|
|
128
|
+
/** Returns true when this token is an alias to another token. */
|
|
129
|
+
isAlias() {
|
|
130
|
+
return this.#value instanceof TokenReference;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/core/model/values/BorderValue.ts
|
|
135
|
+
/**
|
|
136
|
+
* Represents a border style combining color, width, and stroke style.
|
|
137
|
+
*
|
|
138
|
+
* @see https://tr.designtokens.org/format/#border
|
|
139
|
+
*/
|
|
140
|
+
var BorderValue = class {
|
|
141
|
+
color;
|
|
142
|
+
width;
|
|
143
|
+
style;
|
|
144
|
+
constructor(color, width, style) {
|
|
145
|
+
this.color = color;
|
|
146
|
+
this.width = width;
|
|
147
|
+
this.style = style;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/core/model/values/GradientValue.ts
|
|
152
|
+
/**
|
|
153
|
+
* A single stop on a gradient.
|
|
154
|
+
* Position is a number in [0, 1] where 0 is the start and 1 is the end.
|
|
155
|
+
*
|
|
156
|
+
* @see https://tr.designtokens.org/format/#gradient
|
|
157
|
+
*/
|
|
158
|
+
var GradientStop = class {
|
|
159
|
+
color;
|
|
160
|
+
/** Position along the gradient axis, in range [0, 1]. */
|
|
161
|
+
position;
|
|
162
|
+
constructor(color, position) {
|
|
163
|
+
this.color = color;
|
|
164
|
+
this.position = position;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/core/model/values/ShadowValue.ts
|
|
169
|
+
/**
|
|
170
|
+
* A single shadow layer.
|
|
171
|
+
*
|
|
172
|
+
* @see https://tr.designtokens.org/format/#shadow
|
|
173
|
+
*/
|
|
174
|
+
var ShadowLayer = class {
|
|
175
|
+
color;
|
|
176
|
+
offsetX;
|
|
177
|
+
offsetY;
|
|
178
|
+
blur;
|
|
179
|
+
spread;
|
|
180
|
+
/** When true, renders as an inner shadow. Defaults to false. */
|
|
181
|
+
inset;
|
|
182
|
+
constructor(color, offsetX, offsetY, blur, spread, inset = false) {
|
|
183
|
+
this.color = color;
|
|
184
|
+
this.offsetX = offsetX;
|
|
185
|
+
this.offsetY = offsetY;
|
|
186
|
+
this.blur = blur;
|
|
187
|
+
this.spread = spread;
|
|
188
|
+
this.inset = inset;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/core/model/values/StrokeStyleValue.ts
|
|
193
|
+
/**
|
|
194
|
+
* A custom stroke style defined by a dash array and line cap.
|
|
195
|
+
*
|
|
196
|
+
* @see https://tr.designtokens.org/format/#stroke-style
|
|
197
|
+
*/
|
|
198
|
+
var StrokeStyleObject = class {
|
|
199
|
+
/** Alternating dash and gap lengths. */
|
|
200
|
+
dashArray;
|
|
201
|
+
lineCap;
|
|
202
|
+
constructor(dashArray, lineCap) {
|
|
203
|
+
this.dashArray = dashArray;
|
|
204
|
+
this.lineCap = lineCap;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/core/model/values/TransitionValue.ts
|
|
209
|
+
/**
|
|
210
|
+
* Represents an animated transition between two states.
|
|
211
|
+
*
|
|
212
|
+
* @see https://tr.designtokens.org/format/#transition
|
|
213
|
+
*/
|
|
214
|
+
var TransitionValue = class {
|
|
215
|
+
duration;
|
|
216
|
+
delay;
|
|
217
|
+
timingFunction;
|
|
218
|
+
constructor(duration, delay, timingFunction) {
|
|
219
|
+
this.duration = duration;
|
|
220
|
+
this.delay = delay;
|
|
221
|
+
this.timingFunction = timingFunction;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/core/model/values/TypographyValue.ts
|
|
226
|
+
/**
|
|
227
|
+
* Represents a complete typographic style.
|
|
228
|
+
* `lineHeight` is a unitless multiplier of `fontSize`.
|
|
229
|
+
*
|
|
230
|
+
* @see https://tr.designtokens.org/format/#typography
|
|
231
|
+
*/
|
|
232
|
+
var TypographyValue = class {
|
|
233
|
+
fontFamily;
|
|
234
|
+
fontSize;
|
|
235
|
+
fontWeight;
|
|
236
|
+
letterSpacing;
|
|
237
|
+
/** Unitless multiplier of fontSize. */
|
|
238
|
+
lineHeight;
|
|
239
|
+
constructor(fontFamily, fontSize, fontWeight, letterSpacing, lineHeight) {
|
|
240
|
+
this.fontFamily = fontFamily;
|
|
241
|
+
this.fontSize = fontSize;
|
|
242
|
+
this.fontWeight = fontWeight;
|
|
243
|
+
this.letterSpacing = letterSpacing;
|
|
244
|
+
this.lineHeight = lineHeight;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/core/model/Dtcg.ts
|
|
249
|
+
/**
|
|
250
|
+
* The root of a DTCG token document, corresponding to a single `.json` file.
|
|
251
|
+
*
|
|
252
|
+
* @see https://tr.designtokens.org/format/#file-format
|
|
253
|
+
*/
|
|
254
|
+
var Dtcg = class {
|
|
255
|
+
#root;
|
|
256
|
+
constructor(root) {
|
|
257
|
+
this.#root = root;
|
|
258
|
+
}
|
|
259
|
+
/** Returns the top-level child token or group with the given name, or undefined. */
|
|
260
|
+
get(name) {
|
|
261
|
+
return this.#root.get(name);
|
|
262
|
+
}
|
|
263
|
+
/** Returns all top-level child names in insertion order. */
|
|
264
|
+
keys() {
|
|
265
|
+
return this.#root.keys();
|
|
266
|
+
}
|
|
267
|
+
/** Returns all top-level child entries as [name, node] pairs in insertion order. */
|
|
268
|
+
entries() {
|
|
269
|
+
return this.#root.entries();
|
|
270
|
+
}
|
|
271
|
+
get size() {
|
|
272
|
+
return this.#root.size;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Validates the document and returns all found issues.
|
|
276
|
+
*
|
|
277
|
+
* @param base - Optional base document to use as fallback when resolving references.
|
|
278
|
+
* Pass the base document when validating a theme file, so that references to tokens
|
|
279
|
+
* defined in the base are resolved correctly.
|
|
280
|
+
*/
|
|
281
|
+
validate(base) {
|
|
282
|
+
const issues = [];
|
|
283
|
+
const seen = /* @__PURE__ */ new Set();
|
|
284
|
+
this.#validateGroup(this.#root, [], issues, seen, base);
|
|
285
|
+
return issues;
|
|
286
|
+
}
|
|
287
|
+
#push(issues, seen, severity, tokenPath, message) {
|
|
288
|
+
const key = `${severity}:${tokenPath}:${message}`;
|
|
289
|
+
if (seen.has(key)) return;
|
|
290
|
+
seen.add(key);
|
|
291
|
+
issues.push({
|
|
292
|
+
tokenPath,
|
|
293
|
+
severity,
|
|
294
|
+
message
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
#validateGroup(group, path, issues, seen, base) {
|
|
298
|
+
const groupPath = path.join(".") || "<root>";
|
|
299
|
+
if (group.extends instanceof TokenReference) this.#checkRef(group.extends, groupPath, issues, seen, new Set([groupPath]), base);
|
|
300
|
+
for (const [key, child] of group.entries()) {
|
|
301
|
+
const childPath = [...path, key];
|
|
302
|
+
if (child instanceof TokenGroup) this.#validateGroup(child, childPath, issues, seen, base);
|
|
303
|
+
else this.#validateToken(child, childPath, issues, seen, base);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
#validateToken(token, path, issues, seen, base) {
|
|
307
|
+
const tokenPath = path.join(".");
|
|
308
|
+
const visiting = new Set([tokenPath]);
|
|
309
|
+
const value = token.value;
|
|
310
|
+
if (value instanceof TokenReference) {
|
|
311
|
+
this.#checkRef(value, tokenPath, issues, seen, visiting, base);
|
|
312
|
+
if (token.type !== void 0) {
|
|
313
|
+
const resolved = this.#resolveChain(value, void 0, base);
|
|
314
|
+
if (resolved instanceof TokenNode && resolved.type !== void 0 && resolved.type !== token.type) this.#push(issues, seen, "error", tokenPath, `type mismatch: token is "${token.type}" but references "${resolved.type}" token {${value.value}}`);
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (value instanceof BorderValue) {
|
|
319
|
+
this.#checkRefField(value.color, tokenPath, issues, seen, visiting, base);
|
|
320
|
+
this.#checkRefField(value.width, tokenPath, issues, seen, visiting, base);
|
|
321
|
+
this.#checkRefField(value.style, tokenPath, issues, seen, visiting, base);
|
|
322
|
+
if (value.style instanceof StrokeStyleObject) for (const d of value.style.dashArray) this.#checkRefField(d, tokenPath, issues, seen, visiting, base);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (value instanceof TransitionValue) {
|
|
326
|
+
this.#checkRefField(value.duration, tokenPath, issues, seen, visiting, base);
|
|
327
|
+
this.#checkRefField(value.delay, tokenPath, issues, seen, visiting, base);
|
|
328
|
+
this.#checkRefField(value.timingFunction, tokenPath, issues, seen, visiting, base);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (value instanceof ShadowLayer) {
|
|
332
|
+
this.#validateShadowLayer(value, tokenPath, issues, seen, visiting, base);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (Array.isArray(value)) {
|
|
336
|
+
const positions = [];
|
|
337
|
+
for (const item of value) if (item instanceof TokenReference) this.#checkRef(item, tokenPath, issues, seen, visiting, base);
|
|
338
|
+
else if (item instanceof ShadowLayer) this.#validateShadowLayer(item, tokenPath, issues, seen, visiting, base);
|
|
339
|
+
else if (item instanceof GradientStop) {
|
|
340
|
+
this.#checkRefField(item.color, tokenPath, issues, seen, visiting, base);
|
|
341
|
+
this.#checkRefField(item.position, tokenPath, issues, seen, visiting, base);
|
|
342
|
+
if (typeof item.position === "number") if (positions.includes(item.position)) this.#push(issues, seen, "error", tokenPath, `gradient has duplicate stop position ${item.position}`);
|
|
343
|
+
else positions.push(item.position);
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (value instanceof TypographyValue) {
|
|
348
|
+
this.#checkRefField(value.fontFamily, tokenPath, issues, seen, visiting, base);
|
|
349
|
+
this.#checkRefField(value.fontSize, tokenPath, issues, seen, visiting, base);
|
|
350
|
+
this.#checkRefField(value.fontWeight, tokenPath, issues, seen, visiting, base);
|
|
351
|
+
this.#checkRefField(value.letterSpacing, tokenPath, issues, seen, visiting, base);
|
|
352
|
+
this.#checkRefField(value.lineHeight, tokenPath, issues, seen, visiting, base);
|
|
353
|
+
if (Array.isArray(value.fontFamily)) {
|
|
354
|
+
for (const f of value.fontFamily) if (f instanceof TokenReference) this.#checkRef(f, tokenPath, issues, seen, visiting, base);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
#validateShadowLayer(layer, tokenPath, issues, seen, visiting, base) {
|
|
359
|
+
this.#checkRefField(layer.color, tokenPath, issues, seen, visiting, base);
|
|
360
|
+
this.#checkRefField(layer.offsetX, tokenPath, issues, seen, visiting, base);
|
|
361
|
+
this.#checkRefField(layer.offsetY, tokenPath, issues, seen, visiting, base);
|
|
362
|
+
this.#checkRefField(layer.blur, tokenPath, issues, seen, visiting, base);
|
|
363
|
+
this.#checkRefField(layer.spread, tokenPath, issues, seen, visiting, base);
|
|
364
|
+
}
|
|
365
|
+
#checkRefField(value, tokenPath, issues, seen, visiting, base) {
|
|
366
|
+
if (value instanceof TokenReference) this.#checkRef(value, tokenPath, issues, seen, visiting, base);
|
|
367
|
+
}
|
|
368
|
+
#checkRef(ref, tokenPath, issues, seen, visiting, base) {
|
|
369
|
+
const target = this.#resolveRef(ref, base);
|
|
370
|
+
if (!target) {
|
|
371
|
+
this.#push(issues, seen, "error", tokenPath, `references {${ref.value}} which does not exist`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (target instanceof TokenGroup) {
|
|
375
|
+
this.#push(issues, seen, "error", tokenPath, `references {${ref.value}} which is a group, not a token`);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (visiting.has(ref.value)) {
|
|
379
|
+
this.#push(issues, seen, "error", tokenPath, `circular reference: {${ref.value}}`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (target.value instanceof TokenReference) {
|
|
383
|
+
visiting.add(ref.value);
|
|
384
|
+
this.#checkRef(target.value, ref.value, issues, seen, visiting, base);
|
|
385
|
+
visiting.delete(ref.value);
|
|
386
|
+
}
|
|
387
|
+
if (target.deprecated) this.#push(issues, seen, "warning", tokenPath, `references deprecated token {${ref.value}}`);
|
|
388
|
+
}
|
|
389
|
+
#resolveRef(ref, base) {
|
|
390
|
+
const parts = ref.value.split(".");
|
|
391
|
+
let node = this.#root.get(parts[0]);
|
|
392
|
+
for (let i = 1; i < parts.length; i++) {
|
|
393
|
+
if (!(node instanceof TokenGroup)) return void 0;
|
|
394
|
+
node = node.get(parts[i]);
|
|
395
|
+
}
|
|
396
|
+
if (node === void 0 && base !== void 0) return base.#resolveRef(ref);
|
|
397
|
+
return node;
|
|
398
|
+
}
|
|
399
|
+
#resolveChain(ref, visited = /* @__PURE__ */ new Set(), base) {
|
|
400
|
+
const target = this.#resolveRef(ref, base);
|
|
401
|
+
if (!(target instanceof TokenNode)) return target;
|
|
402
|
+
if (target.value instanceof TokenReference) {
|
|
403
|
+
if (visited.has(ref.value)) return void 0;
|
|
404
|
+
visited.add(ref.value);
|
|
405
|
+
return this.#resolveChain(target.value, visited, base);
|
|
406
|
+
}
|
|
407
|
+
return target;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/core/model/tokens/AliasToken.ts
|
|
412
|
+
/**
|
|
413
|
+
* A token that is purely an alias to another token with no explicit `$type`.
|
|
414
|
+
*
|
|
415
|
+
* The effective type is determined by the referenced token and is not known
|
|
416
|
+
* until the alias is resolved.
|
|
417
|
+
*
|
|
418
|
+
* @see https://tr.designtokens.org/format/#aliases-references
|
|
419
|
+
*/
|
|
420
|
+
var AliasToken = class extends TokenNode {
|
|
421
|
+
constructor(ref, description, deprecated, extensions) {
|
|
422
|
+
super(void 0, ref, description, deprecated, extensions);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/core/model/tokens/BorderToken.ts
|
|
427
|
+
/**
|
|
428
|
+
* A token representing a border style (color, width, and stroke style).
|
|
429
|
+
*
|
|
430
|
+
* @see https://tr.designtokens.org/format/#border
|
|
431
|
+
*/
|
|
432
|
+
var BorderToken = class extends TokenNode {
|
|
433
|
+
constructor(value, description, deprecated, extensions) {
|
|
434
|
+
super("border", value, description, deprecated, extensions);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/core/model/tokens/ColorToken.ts
|
|
439
|
+
/**
|
|
440
|
+
* A token representing a color value.
|
|
441
|
+
*
|
|
442
|
+
* @see https://tr.designtokens.org/format/#color
|
|
443
|
+
*/
|
|
444
|
+
var ColorToken = class extends TokenNode {
|
|
445
|
+
constructor(value, description, deprecated, extensions) {
|
|
446
|
+
super("color", value, description, deprecated, extensions);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/core/model/tokens/CubicBezierToken.ts
|
|
451
|
+
/**
|
|
452
|
+
* A token representing a cubic-bezier easing function.
|
|
453
|
+
*
|
|
454
|
+
* @see https://tr.designtokens.org/format/#cubic-bezier
|
|
455
|
+
*/
|
|
456
|
+
var CubicBezierToken = class extends TokenNode {
|
|
457
|
+
constructor(value, description, deprecated, extensions) {
|
|
458
|
+
super("cubicBezier", value, description, deprecated, extensions);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
//#endregion
|
|
462
|
+
//#region src/core/model/tokens/DimensionToken.ts
|
|
463
|
+
/**
|
|
464
|
+
* A token representing a distance value (e.g. spacing, size, radius).
|
|
465
|
+
*
|
|
466
|
+
* @see https://tr.designtokens.org/format/#dimension
|
|
467
|
+
*/
|
|
468
|
+
var DimensionToken = class extends TokenNode {
|
|
469
|
+
constructor(value, description, deprecated, extensions) {
|
|
470
|
+
super("dimension", value, description, deprecated, extensions);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/core/model/tokens/DurationToken.ts
|
|
475
|
+
/**
|
|
476
|
+
* A token representing an animation or transition duration.
|
|
477
|
+
*
|
|
478
|
+
* @see https://tr.designtokens.org/format/#duration
|
|
479
|
+
*/
|
|
480
|
+
var DurationToken = class extends TokenNode {
|
|
481
|
+
constructor(value, description, deprecated, extensions) {
|
|
482
|
+
super("duration", value, description, deprecated, extensions);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region src/core/model/tokens/FontFamilyToken.ts
|
|
487
|
+
/**
|
|
488
|
+
* A token representing a font family or a list of font families.
|
|
489
|
+
*
|
|
490
|
+
* @see https://tr.designtokens.org/format/#font-family
|
|
491
|
+
*/
|
|
492
|
+
var FontFamilyToken = class extends TokenNode {
|
|
493
|
+
constructor(value, description, deprecated, extensions) {
|
|
494
|
+
super("fontFamily", value, description, deprecated, extensions);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/core/model/tokens/FontWeightToken.ts
|
|
499
|
+
/**
|
|
500
|
+
* A token representing a font weight as a number (1-1000) or a keyword.
|
|
501
|
+
*
|
|
502
|
+
* @see https://tr.designtokens.org/format/#font-weight
|
|
503
|
+
*/
|
|
504
|
+
var FontWeightToken = class extends TokenNode {
|
|
505
|
+
constructor(value, description, deprecated, extensions) {
|
|
506
|
+
super("fontWeight", value, description, deprecated, extensions);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/core/model/tokens/GradientToken.ts
|
|
511
|
+
/**
|
|
512
|
+
* A token representing a color gradient as an ordered list of stops.
|
|
513
|
+
*
|
|
514
|
+
* @see https://tr.designtokens.org/format/#gradient
|
|
515
|
+
*/
|
|
516
|
+
var GradientToken = class extends TokenNode {
|
|
517
|
+
constructor(value, description, deprecated, extensions) {
|
|
518
|
+
super("gradient", value, description, deprecated, extensions);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/core/model/tokens/NumberToken.ts
|
|
523
|
+
/**
|
|
524
|
+
* A token representing a unitless number (e.g. opacity, line-height multiplier).
|
|
525
|
+
*
|
|
526
|
+
* @see https://tr.designtokens.org/format/#number
|
|
527
|
+
*/
|
|
528
|
+
var NumberToken = class extends TokenNode {
|
|
529
|
+
constructor(value, description, deprecated, extensions) {
|
|
530
|
+
super("number", value, description, deprecated, extensions);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
//#endregion
|
|
534
|
+
//#region src/core/model/tokens/ShadowToken.ts
|
|
535
|
+
/**
|
|
536
|
+
* A token representing a shadow style - either a single layer or a list of layers.
|
|
537
|
+
*
|
|
538
|
+
* @see https://tr.designtokens.org/format/#shadow
|
|
539
|
+
*/
|
|
540
|
+
var ShadowToken = class extends TokenNode {
|
|
541
|
+
constructor(value, description, deprecated, extensions) {
|
|
542
|
+
super("shadow", value, description, deprecated, extensions);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/core/model/tokens/StrokeStyleToken.ts
|
|
547
|
+
/**
|
|
548
|
+
* A token representing a stroke (line) style - either a keyword or a custom dash pattern.
|
|
549
|
+
*
|
|
550
|
+
* @see https://tr.designtokens.org/format/#stroke-style
|
|
551
|
+
*/
|
|
552
|
+
var StrokeStyleToken = class extends TokenNode {
|
|
553
|
+
constructor(value, description, deprecated, extensions) {
|
|
554
|
+
super("strokeStyle", value, description, deprecated, extensions);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
//#endregion
|
|
558
|
+
//#region src/core/model/tokens/TransitionToken.ts
|
|
559
|
+
/**
|
|
560
|
+
* A token representing an animated transition (duration, delay, timing function).
|
|
561
|
+
*
|
|
562
|
+
* @see https://tr.designtokens.org/format/#transition
|
|
563
|
+
*/
|
|
564
|
+
var TransitionToken = class extends TokenNode {
|
|
565
|
+
constructor(value, description, deprecated, extensions) {
|
|
566
|
+
super("transition", value, description, deprecated, extensions);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region src/core/model/tokens/TypographyToken.ts
|
|
571
|
+
/**
|
|
572
|
+
* A token representing a complete typographic style.
|
|
573
|
+
*
|
|
574
|
+
* @see https://tr.designtokens.org/format/#typography
|
|
575
|
+
*/
|
|
576
|
+
var TypographyToken = class extends TokenNode {
|
|
577
|
+
constructor(value, description, deprecated, extensions) {
|
|
578
|
+
super("typography", value, description, deprecated, extensions);
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/core/model/values/ColorValue.ts
|
|
583
|
+
/**
|
|
584
|
+
* Represents a color value in a specific color space.
|
|
585
|
+
*
|
|
586
|
+
* Components are ordered as defined by the color space (e.g., [R, G, B] for sRGB,
|
|
587
|
+
* [H, S, L] for HSL). The `hex` field is an optional 6-digit CSS fallback.
|
|
588
|
+
*
|
|
589
|
+
* @see https://tr.designtokens.org/format/#color
|
|
590
|
+
*/
|
|
591
|
+
var ColorValue = class {
|
|
592
|
+
colorSpace;
|
|
593
|
+
components;
|
|
594
|
+
/** Alpha channel, 0 (transparent) to 1 (opaque). Defaults to 1. */
|
|
595
|
+
alpha;
|
|
596
|
+
/** Optional 6-digit hex fallback, e.g. `#ff00ff`. */
|
|
597
|
+
hex;
|
|
598
|
+
constructor(colorSpace, components, alpha = 1, hex) {
|
|
599
|
+
this.colorSpace = colorSpace;
|
|
600
|
+
this.components = components;
|
|
601
|
+
this.alpha = alpha;
|
|
602
|
+
this.hex = hex;
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/core/model/values/CubicBezierValue.ts
|
|
607
|
+
/**
|
|
608
|
+
* Represents a CSS cubic-bezier timing function with two control points P1 and P2.
|
|
609
|
+
*
|
|
610
|
+
* P0 is implicitly (0, 0) and P3 is implicitly (1, 1).
|
|
611
|
+
* X coordinates must be in [0, 1]; Y coordinates are unbounded.
|
|
612
|
+
*
|
|
613
|
+
* @see https://tr.designtokens.org/format/#cubic-bezier
|
|
614
|
+
*/
|
|
615
|
+
var CubicBezierValue = class {
|
|
616
|
+
/** X coordinate of control point P1, in range [0, 1]. */
|
|
617
|
+
p1x;
|
|
618
|
+
/** Y coordinate of control point P1. */
|
|
619
|
+
p1y;
|
|
620
|
+
/** X coordinate of control point P2, in range [0, 1]. */
|
|
621
|
+
p2x;
|
|
622
|
+
/** Y coordinate of control point P2. */
|
|
623
|
+
p2y;
|
|
624
|
+
constructor(p1x, p1y, p2x, p2y) {
|
|
625
|
+
this.p1x = p1x;
|
|
626
|
+
this.p1y = p1y;
|
|
627
|
+
this.p2x = p2x;
|
|
628
|
+
this.p2y = p2y;
|
|
629
|
+
}
|
|
630
|
+
toString() {
|
|
631
|
+
return `cubic-bezier(${this.p1x}, ${this.p1y}, ${this.p2x}, ${this.p2y})`;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/core/model/values/DimensionValue.ts
|
|
636
|
+
/**
|
|
637
|
+
* Represents a distance value with a unit.
|
|
638
|
+
*
|
|
639
|
+
* @see https://tr.designtokens.org/format/#dimension
|
|
640
|
+
*/
|
|
641
|
+
var DimensionValue = class {
|
|
642
|
+
value;
|
|
643
|
+
unit;
|
|
644
|
+
constructor(value, unit) {
|
|
645
|
+
this.value = value;
|
|
646
|
+
this.unit = unit;
|
|
647
|
+
}
|
|
648
|
+
toString() {
|
|
649
|
+
return `${this.value}${this.unit}`;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/core/model/values/DurationValue.ts
|
|
654
|
+
/**
|
|
655
|
+
* Represents an animation or transition duration.
|
|
656
|
+
*
|
|
657
|
+
* @see https://tr.designtokens.org/format/#duration
|
|
658
|
+
*/
|
|
659
|
+
var DurationValue = class {
|
|
660
|
+
value;
|
|
661
|
+
unit;
|
|
662
|
+
constructor(value, unit) {
|
|
663
|
+
this.value = value;
|
|
664
|
+
this.unit = unit;
|
|
665
|
+
}
|
|
666
|
+
/** Returns the duration normalized to milliseconds. */
|
|
667
|
+
toMs() {
|
|
668
|
+
return this.unit === "ms" ? this.value : this.value * 1e3;
|
|
669
|
+
}
|
|
670
|
+
toString() {
|
|
671
|
+
return `${this.value}${this.unit}`;
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/core/io/HrdtTokenReader.ts
|
|
676
|
+
var DIMENSION_RE = /^(-?\d+(?:\.\d+)?)(px|rem)$/;
|
|
677
|
+
var DURATION_RE = /^(-?\d+(?:\.\d+)?)(ms|s)$/;
|
|
678
|
+
var HEX_RE = /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
679
|
+
var REFERENCE_RE = /^\{[^{}]+\}$/;
|
|
680
|
+
var TOKEN_TYPES = new Set([
|
|
681
|
+
"color",
|
|
682
|
+
"dimension",
|
|
683
|
+
"fontFamily",
|
|
684
|
+
"fontWeight",
|
|
685
|
+
"duration",
|
|
686
|
+
"cubicBezier",
|
|
687
|
+
"number",
|
|
688
|
+
"strokeStyle",
|
|
689
|
+
"border",
|
|
690
|
+
"transition",
|
|
691
|
+
"shadow",
|
|
692
|
+
"gradient",
|
|
693
|
+
"typography"
|
|
694
|
+
]);
|
|
695
|
+
/**
|
|
696
|
+
* Reads an HRDT token file and parses it directly into a {@link Dtcg} model.
|
|
697
|
+
*
|
|
698
|
+
* The HRDT format uses YAML syntax and group path to infer token types
|
|
699
|
+
* (e.g. `primitive.color.*` -> color tokens). Non-primitive groups
|
|
700
|
+
* contain alias references to primitive tokens.
|
|
701
|
+
*/
|
|
702
|
+
var HrdtTokenReader = class {
|
|
703
|
+
parseRaw(hrdtContent) {
|
|
704
|
+
return new SimpleHrdtParser(hrdtContent).parse();
|
|
705
|
+
}
|
|
706
|
+
parse(hrdtContent) {
|
|
707
|
+
return new Dtcg(this.#parseRoot(new SimpleHrdtParser(hrdtContent).parse()));
|
|
708
|
+
}
|
|
709
|
+
async parseFile(filePath) {
|
|
710
|
+
return this.parse(await readFile(filePath, "utf8"));
|
|
711
|
+
}
|
|
712
|
+
#parseRoot(raw) {
|
|
713
|
+
const children = /* @__PURE__ */ new Map();
|
|
714
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
715
|
+
if (key === "$schema") continue;
|
|
716
|
+
if (!this.#isObject(value)) continue;
|
|
717
|
+
if (key === "primitive") children.set(key, this.#parsePrimitiveRoot(value));
|
|
718
|
+
else children.set(key, this.#parseReferenceGroup(value));
|
|
719
|
+
}
|
|
720
|
+
return new TokenGroup({ children });
|
|
721
|
+
}
|
|
722
|
+
#parsePrimitiveRoot(raw) {
|
|
723
|
+
const children = /* @__PURE__ */ new Map();
|
|
724
|
+
for (const [typeName, tokens] of Object.entries(raw)) {
|
|
725
|
+
if (!TOKEN_TYPES.has(typeName)) throw new HrdtTokenReaderError(`Unknown primitive token type: "${typeName}"`);
|
|
726
|
+
const tokenType = typeName;
|
|
727
|
+
children.set(typeName, this.#parsePrimitiveTypeGroup(tokens, tokenType));
|
|
728
|
+
}
|
|
729
|
+
return new TokenGroup({ children });
|
|
730
|
+
}
|
|
731
|
+
#parsePrimitiveTypeGroup(raw, tokenType) {
|
|
732
|
+
const children = /* @__PURE__ */ new Map();
|
|
733
|
+
for (const [name, value] of Object.entries(raw)) children.set(name, this.#parsePrimitiveToken(value, tokenType));
|
|
734
|
+
return new TokenGroup({
|
|
735
|
+
type: tokenType,
|
|
736
|
+
children
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
#parsePrimitiveToken(value, tokenType) {
|
|
740
|
+
if (typeof value === "string" && REFERENCE_RE.test(value)) return new AliasToken(new TokenReference(value.slice(1, -1)));
|
|
741
|
+
switch (tokenType) {
|
|
742
|
+
case "color": return new ColorToken(this.#parseColor(value));
|
|
743
|
+
case "dimension": return new DimensionToken(this.#parseDimension(value));
|
|
744
|
+
case "fontFamily": return new FontFamilyToken(this.#parseFontFamily(value));
|
|
745
|
+
case "fontWeight": return new FontWeightToken(this.#parseFontWeight(value));
|
|
746
|
+
case "number": return new NumberToken(this.#parseNumber(value));
|
|
747
|
+
case "duration": return new DurationToken(this.#parseDuration(value));
|
|
748
|
+
case "cubicBezier": return new CubicBezierToken(this.#parseCubicBezier(value));
|
|
749
|
+
case "strokeStyle": return new StrokeStyleToken(this.#parseStrokeStyle(value));
|
|
750
|
+
case "border": return new BorderToken(this.#parseBorder(value));
|
|
751
|
+
case "transition": return new TransitionToken(this.#parseTransition(value));
|
|
752
|
+
case "shadow": return new ShadowToken(this.#parseShadow(value));
|
|
753
|
+
case "gradient": return new GradientToken(this.#parseGradient(value));
|
|
754
|
+
case "typography": return new TypographyToken(this.#parseTypography(value));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
#parseReferenceGroup(raw) {
|
|
758
|
+
const children = /* @__PURE__ */ new Map();
|
|
759
|
+
for (const [key, value] of Object.entries(raw)) if (this.#isLeaf(value)) children.set(key, this.#parseReferenceToken(value));
|
|
760
|
+
else children.set(key, this.#parseReferenceGroup(value));
|
|
761
|
+
return new TokenGroup({ children });
|
|
762
|
+
}
|
|
763
|
+
#isLeaf(value) {
|
|
764
|
+
return !this.#isObject(value) || Array.isArray(value);
|
|
765
|
+
}
|
|
766
|
+
#parseReferenceToken(value) {
|
|
767
|
+
if (typeof value === "string" && REFERENCE_RE.test(value)) return new AliasToken(new TokenReference(value.slice(1, -1)));
|
|
768
|
+
throw new HrdtTokenReaderError(`Expected a reference in non-primitive group, got: ${JSON.stringify(value)}`);
|
|
769
|
+
}
|
|
770
|
+
#parseColor(value) {
|
|
771
|
+
if (typeof value !== "string" || !HEX_RE.test(value)) throw new HrdtTokenReaderError(`Expected hex color, got: ${JSON.stringify(value)}`);
|
|
772
|
+
const normalized = value.toLowerCase();
|
|
773
|
+
const hex = normalized.slice(0, 7);
|
|
774
|
+
const r = parseInt(normalized.slice(1, 3), 16);
|
|
775
|
+
const g = parseInt(normalized.slice(3, 5), 16);
|
|
776
|
+
const b = parseInt(normalized.slice(5, 7), 16);
|
|
777
|
+
const alpha = normalized.length === 9 ? this.#round(parseInt(normalized.slice(7, 9), 16) / 255) : 1;
|
|
778
|
+
return new ColorValue("srgb", [
|
|
779
|
+
this.#round(r / 255),
|
|
780
|
+
this.#round(g / 255),
|
|
781
|
+
this.#round(b / 255)
|
|
782
|
+
], alpha, hex);
|
|
783
|
+
}
|
|
784
|
+
#parseDimension(value) {
|
|
785
|
+
if (typeof value !== "string") throw new HrdtTokenReaderError(`Expected dimension string, got: ${JSON.stringify(value)}`);
|
|
786
|
+
const match = value.match(DIMENSION_RE);
|
|
787
|
+
if (!match) throw new HrdtTokenReaderError(`Expected dimension with px/rem unit, got: "${value}"`);
|
|
788
|
+
return new DimensionValue(Number(match[1]), match[2]);
|
|
789
|
+
}
|
|
790
|
+
#parseFontFamily(value) {
|
|
791
|
+
if (typeof value === "string") return value;
|
|
792
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string")) return value;
|
|
793
|
+
throw new HrdtTokenReaderError(`Expected fontFamily string or array, got: ${JSON.stringify(value)}`);
|
|
794
|
+
}
|
|
795
|
+
#parseFontWeight(value) {
|
|
796
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
797
|
+
throw new HrdtTokenReaderError(`Expected fontWeight string or number, got: ${JSON.stringify(value)}`);
|
|
798
|
+
}
|
|
799
|
+
#parseNumber(value) {
|
|
800
|
+
if (typeof value !== "number") throw new HrdtTokenReaderError(`Expected number, got: ${JSON.stringify(value)}`);
|
|
801
|
+
return value;
|
|
802
|
+
}
|
|
803
|
+
#parseDuration(value) {
|
|
804
|
+
if (typeof value !== "string") throw new HrdtTokenReaderError(`Expected duration string, got: ${JSON.stringify(value)}`);
|
|
805
|
+
const match = value.match(DURATION_RE);
|
|
806
|
+
if (!match) throw new HrdtTokenReaderError(`Expected duration with ms/s unit, got: "${value}"`);
|
|
807
|
+
return new DurationValue(Number(match[1]), match[2]);
|
|
808
|
+
}
|
|
809
|
+
#parseCubicBezier(value) {
|
|
810
|
+
if (!Array.isArray(value) || value.length !== 4 || !value.every((v) => typeof v === "number")) throw new HrdtTokenReaderError(`Expected cubicBezier [n, n, n, n], got: ${JSON.stringify(value)}`);
|
|
811
|
+
return new CubicBezierValue(value[0], value[1], value[2], value[3]);
|
|
812
|
+
}
|
|
813
|
+
#parseStrokeStyle(value) {
|
|
814
|
+
if (typeof value === "string") return value;
|
|
815
|
+
if (this.#isObject(value)) {
|
|
816
|
+
const obj = value;
|
|
817
|
+
return new StrokeStyleObject(obj["dashArray"].map((d) => this.#parseDimension(d)), obj["lineCap"]);
|
|
818
|
+
}
|
|
819
|
+
throw new HrdtTokenReaderError(`Expected strokeStyle, got: ${JSON.stringify(value)}`);
|
|
820
|
+
}
|
|
821
|
+
#parseBorder(value) {
|
|
822
|
+
if (!this.#isObject(value)) throw new HrdtTokenReaderError(`Expected border object, got: ${JSON.stringify(value)}`);
|
|
823
|
+
const obj = value;
|
|
824
|
+
return new BorderValue(this.#parseColor(obj["color"]), this.#parseDimension(obj["width"]), this.#parseStrokeStyle(obj["style"]));
|
|
825
|
+
}
|
|
826
|
+
#parseTransition(value) {
|
|
827
|
+
if (!this.#isObject(value)) throw new HrdtTokenReaderError(`Expected transition object, got: ${JSON.stringify(value)}`);
|
|
828
|
+
const obj = value;
|
|
829
|
+
return new TransitionValue(this.#parseDuration(obj["duration"]), this.#parseDuration(obj["delay"]), this.#parseCubicBezier(obj["timingFunction"]));
|
|
830
|
+
}
|
|
831
|
+
#parseShadow(value) {
|
|
832
|
+
if (Array.isArray(value)) return value.map((item) => this.#parseShadowLayer(item));
|
|
833
|
+
return this.#parseShadowLayer(value);
|
|
834
|
+
}
|
|
835
|
+
#parseShadowLayer(value) {
|
|
836
|
+
if (!this.#isObject(value)) throw new HrdtTokenReaderError(`Expected shadow layer object, got: ${JSON.stringify(value)}`);
|
|
837
|
+
const obj = value;
|
|
838
|
+
return new ShadowLayer(this.#parseColor(obj["color"]), this.#parseDimension(obj["offsetX"]), this.#parseDimension(obj["offsetY"]), this.#parseDimension(obj["blur"]), this.#parseDimension(obj["spread"]));
|
|
839
|
+
}
|
|
840
|
+
#parseGradient(value) {
|
|
841
|
+
if (!Array.isArray(value)) throw new HrdtTokenReaderError(`Expected gradient stops array, got: ${JSON.stringify(value)}`);
|
|
842
|
+
return value.map((item) => {
|
|
843
|
+
if (!this.#isObject(item)) throw new HrdtTokenReaderError(`Expected gradient stop object, got: ${JSON.stringify(item)}`);
|
|
844
|
+
const obj = item;
|
|
845
|
+
return new GradientStop(this.#parseColor(obj["color"]), this.#parseNumber(obj["position"]));
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
#parseTypography(value) {
|
|
849
|
+
if (!this.#isObject(value)) throw new HrdtTokenReaderError(`Expected typography object, got: ${JSON.stringify(value)}`);
|
|
850
|
+
const obj = value;
|
|
851
|
+
return new TypographyValue(this.#parseFontFamily(obj["fontFamily"]), this.#parseDimension(obj["fontSize"]), this.#parseFontWeight(obj["fontWeight"]), this.#parseDimension(obj["letterSpacing"]), this.#parseNumber(obj["lineHeight"]));
|
|
852
|
+
}
|
|
853
|
+
#isObject(value) {
|
|
854
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
855
|
+
}
|
|
856
|
+
#round(value) {
|
|
857
|
+
return Number(value.toFixed(3));
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
/**
|
|
861
|
+
* Thrown when the YAML content does not conform to the HRDT token format.
|
|
862
|
+
*/
|
|
863
|
+
var HrdtTokenReaderError = class extends Error {
|
|
864
|
+
constructor(message) {
|
|
865
|
+
super(message);
|
|
866
|
+
this.name = "HrdtTokenReaderError";
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
var SimpleHrdtParser = class {
|
|
870
|
+
#lines;
|
|
871
|
+
#index = 0;
|
|
872
|
+
constructor(content) {
|
|
873
|
+
this.#lines = content.replace(/^/, "").split(/\r?\n/g).map((line) => line.replace(/\s+$/u, "")).filter((line) => line.trim().length > 0 && !line.trimStart().startsWith("#")).map((line) => ({
|
|
874
|
+
indent: line.length - line.trimStart().length,
|
|
875
|
+
text: line.trimStart()
|
|
876
|
+
}));
|
|
877
|
+
}
|
|
878
|
+
parse() {
|
|
879
|
+
const result = this.#parseBlock(0);
|
|
880
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) throw new HrdtTokenReaderError("YAML root must be an object.");
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
#parseBlock(indent) {
|
|
884
|
+
const line = this.#current();
|
|
885
|
+
if (!line || line.indent < indent) return {};
|
|
886
|
+
return line.text.startsWith("- ") ? this.#parseSequence(indent) : this.#parseMapping(indent);
|
|
887
|
+
}
|
|
888
|
+
#parseMapping(indent) {
|
|
889
|
+
const output = {};
|
|
890
|
+
while (this.#index < this.#lines.length) {
|
|
891
|
+
const line = this.#current();
|
|
892
|
+
if (line.indent < indent) break;
|
|
893
|
+
if (line.indent > indent) throw new HrdtTokenReaderError(`Unexpected indentation near "${line.text}".`);
|
|
894
|
+
if (line.text.startsWith("- ")) break;
|
|
895
|
+
const { key, value } = this.#parseKeyValue(line.text);
|
|
896
|
+
this.#index += 1;
|
|
897
|
+
output[key] = value === void 0 ? this.#parseBlock(this.#nextIndent(indent)) : this.#parseScalar(value);
|
|
898
|
+
}
|
|
899
|
+
return output;
|
|
900
|
+
}
|
|
901
|
+
#parseSequence(indent) {
|
|
902
|
+
const output = [];
|
|
903
|
+
while (this.#index < this.#lines.length) {
|
|
904
|
+
const line = this.#current();
|
|
905
|
+
if (line.indent < indent) break;
|
|
906
|
+
if (line.indent !== indent || !line.text.startsWith("- ")) break;
|
|
907
|
+
const itemText = line.text.slice(2).trim();
|
|
908
|
+
this.#index += 1;
|
|
909
|
+
if (itemText.includes(":")) {
|
|
910
|
+
const { key, value } = this.#parseKeyValue(itemText);
|
|
911
|
+
const item = {};
|
|
912
|
+
item[key] = value === void 0 ? this.#parseBlock(this.#nextIndent(indent)) : this.#parseScalar(value);
|
|
913
|
+
if (this.#current()?.indent === indent + 2 && !this.#current()?.text.startsWith("- ")) Object.assign(item, this.#parseMapping(indent + 2));
|
|
914
|
+
output.push(item);
|
|
915
|
+
} else output.push(this.#parseScalar(itemText));
|
|
916
|
+
}
|
|
917
|
+
return output;
|
|
918
|
+
}
|
|
919
|
+
#parseKeyValue(text) {
|
|
920
|
+
const delimiter = text.indexOf(":");
|
|
921
|
+
if (delimiter < 0) throw new HrdtTokenReaderError(`Expected key-value pair, got "${text}".`);
|
|
922
|
+
const rawKey = text.slice(0, delimiter).trim();
|
|
923
|
+
if (!rawKey) throw new HrdtTokenReaderError(`Expected non-empty key in "${text}".`);
|
|
924
|
+
const value = text.slice(delimiter + 1).trim();
|
|
925
|
+
const key = rawKey.startsWith("\"") && rawKey.endsWith("\"") || rawKey.startsWith("'") && rawKey.endsWith("'") ? this.#unquote(rawKey) : rawKey;
|
|
926
|
+
return value.length === 0 ? { key } : {
|
|
927
|
+
key,
|
|
928
|
+
value
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
#parseScalar(value) {
|
|
932
|
+
if (value.startsWith("[") && value.endsWith("]")) return this.#parseInlineArray(value);
|
|
933
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return this.#unquote(value);
|
|
934
|
+
if (value === "true") return true;
|
|
935
|
+
if (value === "false") return false;
|
|
936
|
+
if (value === "null") return null;
|
|
937
|
+
if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
|
|
938
|
+
return value;
|
|
939
|
+
}
|
|
940
|
+
#parseInlineArray(value) {
|
|
941
|
+
const inner = value.slice(1, -1).trim();
|
|
942
|
+
if (!inner) return [];
|
|
943
|
+
return this.#splitInlineArray(inner).map((item) => this.#parseScalar(item.trim()));
|
|
944
|
+
}
|
|
945
|
+
#splitInlineArray(value) {
|
|
946
|
+
const items = [];
|
|
947
|
+
let current = "";
|
|
948
|
+
let quote;
|
|
949
|
+
for (let i = 0; i < value.length; i++) {
|
|
950
|
+
const char = value[i];
|
|
951
|
+
if (quote) {
|
|
952
|
+
current += char;
|
|
953
|
+
if (char === quote && value[i - 1] !== "\\") quote = void 0;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (char === "\"" || char === "'") {
|
|
957
|
+
quote = char;
|
|
958
|
+
current += char;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (char === ",") {
|
|
962
|
+
items.push(current);
|
|
963
|
+
current = "";
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
current += char;
|
|
967
|
+
}
|
|
968
|
+
items.push(current);
|
|
969
|
+
return items;
|
|
970
|
+
}
|
|
971
|
+
#unquote(value) {
|
|
972
|
+
if (value.startsWith("\"")) return JSON.parse(value);
|
|
973
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
974
|
+
}
|
|
975
|
+
#current() {
|
|
976
|
+
return this.#lines[this.#index];
|
|
977
|
+
}
|
|
978
|
+
#nextIndent(currentIndent) {
|
|
979
|
+
const nextLine = this.#current();
|
|
980
|
+
if (!nextLine || nextLine.indent <= currentIndent) return currentIndent + 2;
|
|
981
|
+
return nextLine.indent;
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/core/model/DtcgList.ts
|
|
986
|
+
/**
|
|
987
|
+
* An ordered collection of DTCG documents representing a base token set and its theme overrides.
|
|
988
|
+
*
|
|
989
|
+
* The base document defines all tokens. Each theme document overrides a subset of them.
|
|
990
|
+
* When validating a theme, references are resolved against the theme first, then the base.
|
|
991
|
+
*
|
|
992
|
+
* @see https://tr.designtokens.org/format/#file-format
|
|
993
|
+
*/
|
|
994
|
+
var DtcgList = class {
|
|
995
|
+
base;
|
|
996
|
+
themes;
|
|
997
|
+
constructor(base, themes) {
|
|
998
|
+
this.base = base;
|
|
999
|
+
this.themes = themes ?? /* @__PURE__ */ new Map();
|
|
1000
|
+
}
|
|
1001
|
+
validate() {
|
|
1002
|
+
const issues = this.base.validate();
|
|
1003
|
+
for (const theme of this.themes.values()) issues.push(...theme.validate(this.base));
|
|
1004
|
+
return issues;
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/core/io/DtcgJsonReader.ts
|
|
1009
|
+
var GROUP_KEYS = new Set([
|
|
1010
|
+
"$type",
|
|
1011
|
+
"$description",
|
|
1012
|
+
"$extensions",
|
|
1013
|
+
"$deprecated",
|
|
1014
|
+
"$extends",
|
|
1015
|
+
"$root",
|
|
1016
|
+
"$schema"
|
|
1017
|
+
]);
|
|
1018
|
+
/**
|
|
1019
|
+
* Parses a DTCG 2025.10 JSON document into a {@link Dtcg}.
|
|
1020
|
+
*
|
|
1021
|
+
* A child object is treated as a token when it contains `$value` or `$ref`,
|
|
1022
|
+
* and as a group otherwise.
|
|
1023
|
+
*
|
|
1024
|
+
* @see https://tr.designtokens.org/format/
|
|
1025
|
+
*/
|
|
1026
|
+
var DtcgJsonReader = class {
|
|
1027
|
+
parse(content) {
|
|
1028
|
+
const raw = JSON.parse(content);
|
|
1029
|
+
return new Dtcg(this.#parseGroup(raw, void 0));
|
|
1030
|
+
}
|
|
1031
|
+
#parseGroup(raw, inheritedType) {
|
|
1032
|
+
const type = this.#resolveType(raw["$type"], inheritedType);
|
|
1033
|
+
const description = typeof raw["$description"] === "string" ? raw["$description"] : void 0;
|
|
1034
|
+
const deprecated = this.#parseDeprecated(raw["$deprecated"]);
|
|
1035
|
+
const extensions = this.#parseExtensions(raw["$extensions"]);
|
|
1036
|
+
const extendsRef = this.#parseExtendsRef(raw["$extends"]);
|
|
1037
|
+
const root = raw["$root"] != null ? this.#parseToken(raw["$root"], type) : void 0;
|
|
1038
|
+
const children = /* @__PURE__ */ new Map();
|
|
1039
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1040
|
+
if (GROUP_KEYS.has(key)) continue;
|
|
1041
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
|
|
1042
|
+
const child = value;
|
|
1043
|
+
if (this.#isToken(child)) children.set(key, this.#parseToken(child, type));
|
|
1044
|
+
else children.set(key, this.#parseGroup(child, type));
|
|
1045
|
+
}
|
|
1046
|
+
return new TokenGroup({
|
|
1047
|
+
type,
|
|
1048
|
+
description,
|
|
1049
|
+
deprecated,
|
|
1050
|
+
extensions,
|
|
1051
|
+
extends: extendsRef,
|
|
1052
|
+
root,
|
|
1053
|
+
children
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
#isToken(raw) {
|
|
1057
|
+
return "$value" in raw || "$ref" in raw;
|
|
1058
|
+
}
|
|
1059
|
+
#parseToken(raw, inheritedType) {
|
|
1060
|
+
const type = this.#resolveType(raw["$type"], inheritedType);
|
|
1061
|
+
const description = typeof raw["$description"] === "string" ? raw["$description"] : void 0;
|
|
1062
|
+
const deprecated = this.#parseDeprecated(raw["$deprecated"]);
|
|
1063
|
+
const extensions = this.#parseExtensions(raw["$extensions"]);
|
|
1064
|
+
if ("$ref" in raw) {
|
|
1065
|
+
const ref = raw["$ref"];
|
|
1066
|
+
if (typeof ref !== "string") throw new DtcgJsonReaderError("$ref must be a string");
|
|
1067
|
+
return this.#makeToken(type, new TokenReference(ref), description, deprecated, extensions);
|
|
1068
|
+
}
|
|
1069
|
+
const rawValue = raw["$value"];
|
|
1070
|
+
if (typeof rawValue === "string" && /^\{[^{}]+\}$/.test(rawValue)) {
|
|
1071
|
+
const ref = new TokenReference(rawValue.slice(1, -1));
|
|
1072
|
+
return this.#makeToken(type, ref, description, deprecated, extensions);
|
|
1073
|
+
}
|
|
1074
|
+
if (type == null) throw new DtcgJsonReaderError(`Token has no $type and no inherited type: ${JSON.stringify(raw)}`);
|
|
1075
|
+
const value = this.#parseValue(type, rawValue);
|
|
1076
|
+
return this.#makeToken(type, value, description, deprecated, extensions);
|
|
1077
|
+
}
|
|
1078
|
+
#makeToken(type, value, description, deprecated, extensions) {
|
|
1079
|
+
switch (type) {
|
|
1080
|
+
case "color": return new ColorToken(value, description, deprecated, extensions);
|
|
1081
|
+
case "dimension": return new DimensionToken(value, description, deprecated, extensions);
|
|
1082
|
+
case "fontFamily": return new FontFamilyToken(value, description, deprecated, extensions);
|
|
1083
|
+
case "fontWeight": return new FontWeightToken(value, description, deprecated, extensions);
|
|
1084
|
+
case "number": return new NumberToken(value, description, deprecated, extensions);
|
|
1085
|
+
case "duration": return new DurationToken(value, description, deprecated, extensions);
|
|
1086
|
+
case "cubicBezier": return new CubicBezierToken(value, description, deprecated, extensions);
|
|
1087
|
+
case "strokeStyle": return new StrokeStyleToken(value, description, deprecated, extensions);
|
|
1088
|
+
case "border": return new BorderToken(value, description, deprecated, extensions);
|
|
1089
|
+
case "transition": return new TransitionToken(value, description, deprecated, extensions);
|
|
1090
|
+
case "shadow": return new ShadowToken(value, description, deprecated, extensions);
|
|
1091
|
+
case "gradient": return new GradientToken(value, description, deprecated, extensions);
|
|
1092
|
+
case "typography": return new TypographyToken(value, description, deprecated, extensions);
|
|
1093
|
+
case void 0: return new AliasToken(value, description, deprecated, extensions);
|
|
1094
|
+
default: throw new DtcgJsonReaderError(`Unknown token type: ${String(type)}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
#parseValue(type, raw) {
|
|
1098
|
+
switch (type) {
|
|
1099
|
+
case "color": return this.#parseColor(raw);
|
|
1100
|
+
case "dimension": return this.#parseDimension(raw);
|
|
1101
|
+
case "fontFamily": return this.#parseFontFamily(raw);
|
|
1102
|
+
case "fontWeight": return this.#parseFontWeight(raw);
|
|
1103
|
+
case "number": return this.#parseNumber(raw);
|
|
1104
|
+
case "duration": return this.#parseDuration(raw);
|
|
1105
|
+
case "cubicBezier": return this.#parseCubicBezier(raw);
|
|
1106
|
+
case "strokeStyle": return this.#parseStrokeStyle(raw);
|
|
1107
|
+
case "border": return this.#parseBorder(raw);
|
|
1108
|
+
case "transition": return this.#parseTransition(raw);
|
|
1109
|
+
case "shadow": return this.#parseShadow(raw);
|
|
1110
|
+
case "gradient": return this.#parseGradient(raw);
|
|
1111
|
+
case "typography": return this.#parseTypography(raw);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
#parseColor(raw) {
|
|
1115
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid color value: ${JSON.stringify(raw)}`);
|
|
1116
|
+
const obj = raw;
|
|
1117
|
+
const colorSpace = obj["colorSpace"];
|
|
1118
|
+
const components = obj["components"];
|
|
1119
|
+
return new ColorValue(colorSpace, components, typeof obj["alpha"] === "number" ? obj["alpha"] : 1, typeof obj["hex"] === "string" ? obj["hex"] : void 0);
|
|
1120
|
+
}
|
|
1121
|
+
#parseDimension(raw) {
|
|
1122
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid dimension value: ${JSON.stringify(raw)}`);
|
|
1123
|
+
const obj = raw;
|
|
1124
|
+
return new DimensionValue(obj["value"], obj["unit"]);
|
|
1125
|
+
}
|
|
1126
|
+
#parseFontFamily(raw) {
|
|
1127
|
+
if (typeof raw === "string") return raw;
|
|
1128
|
+
if (Array.isArray(raw)) return raw;
|
|
1129
|
+
throw new DtcgJsonReaderError(`Invalid fontFamily value: ${JSON.stringify(raw)}`);
|
|
1130
|
+
}
|
|
1131
|
+
#parseFontWeight(raw) {
|
|
1132
|
+
if (typeof raw === "number" || typeof raw === "string") return raw;
|
|
1133
|
+
throw new DtcgJsonReaderError(`Invalid fontWeight value: ${JSON.stringify(raw)}`);
|
|
1134
|
+
}
|
|
1135
|
+
#parseNumber(raw) {
|
|
1136
|
+
if (typeof raw !== "number") throw new DtcgJsonReaderError(`Invalid number value: ${JSON.stringify(raw)}`);
|
|
1137
|
+
return raw;
|
|
1138
|
+
}
|
|
1139
|
+
#parseDuration(raw) {
|
|
1140
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid duration value: ${JSON.stringify(raw)}`);
|
|
1141
|
+
const obj = raw;
|
|
1142
|
+
return new DurationValue(obj["value"], obj["unit"]);
|
|
1143
|
+
}
|
|
1144
|
+
#parseCubicBezier(raw) {
|
|
1145
|
+
if (!Array.isArray(raw) || raw.length !== 4) throw new DtcgJsonReaderError(`Invalid cubicBezier value: ${JSON.stringify(raw)}`);
|
|
1146
|
+
return new CubicBezierValue(raw[0], raw[1], raw[2], raw[3]);
|
|
1147
|
+
}
|
|
1148
|
+
#parseStrokeStyle(raw) {
|
|
1149
|
+
if (typeof raw === "string") return raw;
|
|
1150
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
1151
|
+
const obj = raw;
|
|
1152
|
+
return new StrokeStyleObject(obj["dashArray"].map((d) => this.#parseDimension(d)), obj["lineCap"]);
|
|
1153
|
+
}
|
|
1154
|
+
throw new DtcgJsonReaderError(`Invalid strokeStyle value: ${JSON.stringify(raw)}`);
|
|
1155
|
+
}
|
|
1156
|
+
#parseBorder(raw) {
|
|
1157
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid border value: ${JSON.stringify(raw)}`);
|
|
1158
|
+
const obj = raw;
|
|
1159
|
+
return new BorderValue(this.#parseColorOrRef(obj["color"]), this.#parseDimensionOrRef(obj["width"]), this.#parseStrokeStyleOrRef(obj["style"]));
|
|
1160
|
+
}
|
|
1161
|
+
#parseTransition(raw) {
|
|
1162
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid transition value: ${JSON.stringify(raw)}`);
|
|
1163
|
+
const obj = raw;
|
|
1164
|
+
return new TransitionValue(this.#parseDurationOrRef(obj["duration"]), this.#parseDurationOrRef(obj["delay"]), this.#parseCubicBezierOrRef(obj["timingFunction"]));
|
|
1165
|
+
}
|
|
1166
|
+
#parseShadow(raw) {
|
|
1167
|
+
if (Array.isArray(raw)) return raw.map((item) => this.#parseShadowLayerOrRef(item));
|
|
1168
|
+
return this.#parseShadowLayer(raw);
|
|
1169
|
+
}
|
|
1170
|
+
#parseShadowLayer(raw) {
|
|
1171
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid shadow layer: ${JSON.stringify(raw)}`);
|
|
1172
|
+
const obj = raw;
|
|
1173
|
+
return new ShadowLayer(this.#parseColorOrRef(obj["color"]), this.#parseDimensionOrRef(obj["offsetX"]), this.#parseDimensionOrRef(obj["offsetY"]), this.#parseDimensionOrRef(obj["blur"]), this.#parseDimensionOrRef(obj["spread"]), typeof obj["inset"] === "boolean" ? obj["inset"] : false);
|
|
1174
|
+
}
|
|
1175
|
+
#parseShadowLayerOrRef(raw) {
|
|
1176
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1177
|
+
return this.#parseShadowLayer(raw);
|
|
1178
|
+
}
|
|
1179
|
+
#parseGradient(raw) {
|
|
1180
|
+
if (!Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid gradient value: ${JSON.stringify(raw)}`);
|
|
1181
|
+
return raw.map((item) => this.#parseGradientStopOrRef(item));
|
|
1182
|
+
}
|
|
1183
|
+
#parseGradientStopOrRef(raw) {
|
|
1184
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1185
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid gradient stop: ${JSON.stringify(raw)}`);
|
|
1186
|
+
const obj = raw;
|
|
1187
|
+
return new GradientStop(this.#parseColorOrRef(obj["color"]), this.#parseNumberOrRef(obj["position"]));
|
|
1188
|
+
}
|
|
1189
|
+
#parseTypography(raw) {
|
|
1190
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DtcgJsonReaderError(`Invalid typography value: ${JSON.stringify(raw)}`);
|
|
1191
|
+
const obj = raw;
|
|
1192
|
+
return new TypographyValue(this.#parseFontFamilyOrRef(obj["fontFamily"]), this.#parseDimensionOrRef(obj["fontSize"]), this.#parseFontWeightOrRef(obj["fontWeight"]), this.#parseDimensionOrRef(obj["letterSpacing"]), this.#parseNumberOrRef(obj["lineHeight"]));
|
|
1193
|
+
}
|
|
1194
|
+
#parseColorOrRef(raw) {
|
|
1195
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1196
|
+
return this.#parseColor(raw);
|
|
1197
|
+
}
|
|
1198
|
+
#parseDimensionOrRef(raw) {
|
|
1199
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1200
|
+
return this.#parseDimension(raw);
|
|
1201
|
+
}
|
|
1202
|
+
#parseDurationOrRef(raw) {
|
|
1203
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1204
|
+
return this.#parseDuration(raw);
|
|
1205
|
+
}
|
|
1206
|
+
#parseCubicBezierOrRef(raw) {
|
|
1207
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1208
|
+
return this.#parseCubicBezier(raw);
|
|
1209
|
+
}
|
|
1210
|
+
#parseStrokeStyleOrRef(raw) {
|
|
1211
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1212
|
+
return this.#parseStrokeStyle(raw);
|
|
1213
|
+
}
|
|
1214
|
+
#parseFontFamilyOrRef(raw) {
|
|
1215
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1216
|
+
return this.#parseFontFamily(raw);
|
|
1217
|
+
}
|
|
1218
|
+
#parseFontWeightOrRef(raw) {
|
|
1219
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1220
|
+
return this.#parseFontWeight(raw);
|
|
1221
|
+
}
|
|
1222
|
+
#parseNumberOrRef(raw) {
|
|
1223
|
+
if (typeof raw === "string" && /^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1224
|
+
return this.#parseNumber(raw);
|
|
1225
|
+
}
|
|
1226
|
+
#resolveType(raw, inherited) {
|
|
1227
|
+
if (typeof raw === "string") return raw;
|
|
1228
|
+
return inherited;
|
|
1229
|
+
}
|
|
1230
|
+
#parseDeprecated(raw) {
|
|
1231
|
+
if (typeof raw === "boolean" || typeof raw === "string") return raw;
|
|
1232
|
+
}
|
|
1233
|
+
#parseExtensions(raw) {
|
|
1234
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) return raw;
|
|
1235
|
+
}
|
|
1236
|
+
#parseExtendsRef(raw) {
|
|
1237
|
+
if (typeof raw !== "string") return void 0;
|
|
1238
|
+
if (/^\{[^{}]+\}$/.test(raw)) return new TokenReference(raw.slice(1, -1));
|
|
1239
|
+
return raw;
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
/**
|
|
1243
|
+
* Thrown when the input JSON does not conform to the expected DTCG structure.
|
|
1244
|
+
*/
|
|
1245
|
+
var DtcgJsonReaderError = class extends Error {
|
|
1246
|
+
constructor(message) {
|
|
1247
|
+
super(message);
|
|
1248
|
+
this.name = "DtcgJsonReaderError";
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
//#endregion
|
|
1252
|
+
//#region src/core/Source.ts
|
|
1253
|
+
var SourceType = /* @__PURE__ */ function(SourceType) {
|
|
1254
|
+
SourceType["URL"] = "url";
|
|
1255
|
+
SourceType["FILE"] = "file";
|
|
1256
|
+
SourceType["CONTENT"] = "content";
|
|
1257
|
+
return SourceType;
|
|
1258
|
+
}(SourceType || {});
|
|
1259
|
+
/**
|
|
1260
|
+
* Data source.
|
|
1261
|
+
* Wraps URL, File or content.
|
|
1262
|
+
*
|
|
1263
|
+
* Allows returning any of these data sources as a file.
|
|
1264
|
+
* Used in tool implementations to simplify the interface.
|
|
1265
|
+
*/
|
|
1266
|
+
var Source = class Source {
|
|
1267
|
+
#type;
|
|
1268
|
+
#input;
|
|
1269
|
+
#filePath;
|
|
1270
|
+
constructor(input) {
|
|
1271
|
+
this.#input = input;
|
|
1272
|
+
if (Source.#isHttpUrl(input)) this.#type = SourceType.URL;
|
|
1273
|
+
else if (existsSync(input)) this.#type = SourceType.FILE;
|
|
1274
|
+
else this.#type = SourceType.CONTENT;
|
|
1275
|
+
}
|
|
1276
|
+
static #isHttpUrl(value) {
|
|
1277
|
+
try {
|
|
1278
|
+
const url = new URL(value);
|
|
1279
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1280
|
+
} catch {
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
getInput() {
|
|
1285
|
+
return this.#input;
|
|
1286
|
+
}
|
|
1287
|
+
getType() {
|
|
1288
|
+
return this.#type;
|
|
1289
|
+
}
|
|
1290
|
+
async getContent() {
|
|
1291
|
+
if (this.#type === SourceType.FILE) return readFile(this.#input, "utf8");
|
|
1292
|
+
else if (this.#type === SourceType.URL) {
|
|
1293
|
+
const response = await fetch(this.#input);
|
|
1294
|
+
if (!response.ok) throw new Error(`Unable to fetch "${this.#input}": HTTP ${response.status}`);
|
|
1295
|
+
return response.text();
|
|
1296
|
+
} else if (this.#type === SourceType.CONTENT) return this.#input;
|
|
1297
|
+
else throw new Error(`Unsupported source type "${this.#type}: ${this.#input}"`);
|
|
1298
|
+
}
|
|
1299
|
+
async getFile() {
|
|
1300
|
+
if (this.#filePath !== void 0) return this.#filePath;
|
|
1301
|
+
if (this.#type === SourceType.FILE) this.#filePath = this.#input;
|
|
1302
|
+
else if (this.#type === SourceType.URL) {
|
|
1303
|
+
const response = await fetch(this.#input);
|
|
1304
|
+
if (!response.ok) throw new Error(`Unable to fetch "${this.#input}": HTTP ${response.status}`);
|
|
1305
|
+
this.#filePath = await Source.#writeTemp(await response.text());
|
|
1306
|
+
} else this.#filePath = await Source.#writeTemp(this.#input);
|
|
1307
|
+
return this.#filePath;
|
|
1308
|
+
}
|
|
1309
|
+
static async #writeTemp(content) {
|
|
1310
|
+
const filePath = path.join(tmpdir(), `designtokens-${randomUUID()}.tmp`);
|
|
1311
|
+
await writeFile(filePath, content, "utf8");
|
|
1312
|
+
return filePath;
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
//#endregion
|
|
1316
|
+
//#region src/core/css/DtcgTokenCssConverter.ts
|
|
1317
|
+
function extractThemeName$1(filePath) {
|
|
1318
|
+
const withoutExt = (filePath.split("/").at(-1)?.split("\\").at(-1) ?? filePath).replace(/\.json$/i, "");
|
|
1319
|
+
const dotIndex = withoutExt.lastIndexOf(".");
|
|
1320
|
+
return dotIndex > 0 ? withoutExt.slice(dotIndex + 1) : withoutExt;
|
|
1321
|
+
}
|
|
1322
|
+
function tokenPathToCssVar(path) {
|
|
1323
|
+
return `--${path.replace(/\./g, "-")}`;
|
|
1324
|
+
}
|
|
1325
|
+
function refToCssVar(ref) {
|
|
1326
|
+
return `var(${tokenPathToCssVar(ref.value)})`;
|
|
1327
|
+
}
|
|
1328
|
+
function colorToCss(color) {
|
|
1329
|
+
if (color.hex && color.alpha === 1) return color.hex;
|
|
1330
|
+
const components = color.components.map((c) => c === "none" ? "none" : String(c)).join(" ");
|
|
1331
|
+
const alpha = color.alpha !== 1 ? ` / ${color.alpha}` : "";
|
|
1332
|
+
return `color(${color.colorSpace} ${components}${alpha})`;
|
|
1333
|
+
}
|
|
1334
|
+
function dimensionOrRefToCss(value) {
|
|
1335
|
+
if (value instanceof TokenReference) return refToCssVar(value);
|
|
1336
|
+
return `${value.value}${value.unit}`;
|
|
1337
|
+
}
|
|
1338
|
+
function durationOrRefToCss(value) {
|
|
1339
|
+
if (value instanceof TokenReference) return refToCssVar(value);
|
|
1340
|
+
return `${value.value}${value.unit}`;
|
|
1341
|
+
}
|
|
1342
|
+
function timingOrRefToCss(value) {
|
|
1343
|
+
if (value instanceof TokenReference) return refToCssVar(value);
|
|
1344
|
+
return `cubic-bezier(${value.p1x}, ${value.p1y}, ${value.p2x}, ${value.p2y})`;
|
|
1345
|
+
}
|
|
1346
|
+
function shadowLayerToCss(layer) {
|
|
1347
|
+
return `${layer.inset ? "inset " : ""}${dimensionOrRefToCss(layer.offsetX)} ${dimensionOrRefToCss(layer.offsetY)} ${dimensionOrRefToCss(layer.blur)} ${dimensionOrRefToCss(layer.spread)} ${layer.color instanceof TokenReference ? refToCssVar(layer.color) : colorToCss(layer.color)}`;
|
|
1348
|
+
}
|
|
1349
|
+
function tokenValueToCss(value) {
|
|
1350
|
+
if (value instanceof TokenReference) return refToCssVar(value);
|
|
1351
|
+
if (value instanceof ColorValue) return colorToCss(value);
|
|
1352
|
+
if (value instanceof DimensionValue) return `${value.value}${value.unit}`;
|
|
1353
|
+
if (value instanceof DurationValue) return `${value.value}${value.unit}`;
|
|
1354
|
+
if (value instanceof CubicBezierValue) return `cubic-bezier(${value.p1x}, ${value.p1y}, ${value.p2x}, ${value.p2y})`;
|
|
1355
|
+
if (typeof value === "number") return String(value);
|
|
1356
|
+
if (typeof value === "string") return value;
|
|
1357
|
+
if (value instanceof ShadowLayer) return shadowLayerToCss(value);
|
|
1358
|
+
if (value instanceof BorderValue) return `${dimensionOrRefToCss(value.width)} ${value.style instanceof TokenReference ? refToCssVar(value.style) : value.style instanceof StrokeStyleObject ? "dashed" : value.style} ${value.color instanceof TokenReference ? refToCssVar(value.color) : colorToCss(value.color)}`;
|
|
1359
|
+
if (value instanceof TransitionValue) return `${durationOrRefToCss(value.duration)} ${timingOrRefToCss(value.timingFunction)} ${durationOrRefToCss(value.delay)}`;
|
|
1360
|
+
if (Array.isArray(value)) {
|
|
1361
|
+
if (value.length === 0) return void 0;
|
|
1362
|
+
if (value[0] instanceof GradientStop || value[0] instanceof TokenReference && !(value[0] instanceof ShadowLayer)) {
|
|
1363
|
+
if (value.every((item) => item instanceof GradientStop || item instanceof TokenReference)) {
|
|
1364
|
+
if (value.some((item) => item instanceof GradientStop)) return void 0;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (value[0] instanceof ShadowLayer || value[0] instanceof TokenReference) return value.map((item) => item instanceof ShadowLayer ? shadowLayerToCss(item) : refToCssVar(item)).join(", ");
|
|
1368
|
+
return value.map((f) => f instanceof TokenReference ? refToCssVar(f) : `"${f}"`).join(", ");
|
|
1369
|
+
}
|
|
1370
|
+
if (value instanceof TypographyValue) return void 0;
|
|
1371
|
+
}
|
|
1372
|
+
function collectDeclarations(group, path) {
|
|
1373
|
+
const result = [];
|
|
1374
|
+
for (const [key, child] of group.entries()) {
|
|
1375
|
+
const childPath = [...path, key];
|
|
1376
|
+
if (child instanceof TokenGroup) result.push(...collectDeclarations(child, childPath));
|
|
1377
|
+
else if (child instanceof TokenNode) {
|
|
1378
|
+
const css = tokenValueToCss(child.value);
|
|
1379
|
+
if (css !== void 0) result.push([tokenPathToCssVar(childPath.join(".")), css]);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return result;
|
|
1383
|
+
}
|
|
1384
|
+
function collectFromDoc(doc) {
|
|
1385
|
+
const result = [];
|
|
1386
|
+
for (const [key, child] of doc.entries()) if (child instanceof TokenGroup) result.push(...collectDeclarations(child, [key]));
|
|
1387
|
+
else if (child instanceof TokenNode) {
|
|
1388
|
+
const css = tokenValueToCss(child.value);
|
|
1389
|
+
if (css !== void 0) result.push([tokenPathToCssVar(key), css]);
|
|
1390
|
+
}
|
|
1391
|
+
return result;
|
|
1392
|
+
}
|
|
1393
|
+
function renderBlock(selector, declarations) {
|
|
1394
|
+
if (declarations.length === 0) return "";
|
|
1395
|
+
return `${selector} {\n${declarations.map(([prop, val]) => ` ${prop}: ${val};`).join("\n")}\n}`;
|
|
1396
|
+
}
|
|
1397
|
+
async function readDoc$1(source) {
|
|
1398
|
+
const content = await new Source(source).getContent();
|
|
1399
|
+
return /\.(ya?ml)$/i.test(source) ? new HrdtTokenReader().parse(content) : new DtcgJsonReader().parse(content);
|
|
1400
|
+
}
|
|
1401
|
+
async function loadDtcgList(sources) {
|
|
1402
|
+
const [base, ...rest] = await Promise.all(sources.map(readDoc$1));
|
|
1403
|
+
return new DtcgList(base, new Map(rest.map((doc, i) => [extractThemeName$1(sources[i + 1]), doc])));
|
|
1404
|
+
}
|
|
1405
|
+
var DtcgTokenCssConverter = class {
|
|
1406
|
+
async convert(sources) {
|
|
1407
|
+
const list = await loadDtcgList(sources);
|
|
1408
|
+
return this.convertList(list);
|
|
1409
|
+
}
|
|
1410
|
+
convertDocument(doc) {
|
|
1411
|
+
return this.convertList(new DtcgList(doc, /* @__PURE__ */ new Map()));
|
|
1412
|
+
}
|
|
1413
|
+
convertList(list) {
|
|
1414
|
+
const blocks = [];
|
|
1415
|
+
const baseBlock = renderBlock(":root", collectFromDoc(list.base));
|
|
1416
|
+
if (baseBlock) blocks.push(baseBlock);
|
|
1417
|
+
for (const [themeName, theme] of list.themes) {
|
|
1418
|
+
const block = renderBlock(`:root[data-theme="${themeName}"]`, collectFromDoc(theme));
|
|
1419
|
+
if (block) blocks.push(block);
|
|
1420
|
+
}
|
|
1421
|
+
return blocks.join("\n\n");
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
//#endregion
|
|
1425
|
+
//#region src/core/showcase/CssTokenParser.ts
|
|
1426
|
+
/**
|
|
1427
|
+
* Parses generated CSS variables into a structure for the HTML showcase.
|
|
1428
|
+
*
|
|
1429
|
+
* @remarks
|
|
1430
|
+
* Finds `:root { ... }` blocks, extracts CSS custom properties from them
|
|
1431
|
+
* and keeps the link to themes from `Set` and `Modifier` comments.
|
|
1432
|
+
*
|
|
1433
|
+
* Returns:
|
|
1434
|
+
* - a full list of all found tokens;
|
|
1435
|
+
* - a list of tokens grouped by themes.
|
|
1436
|
+
*/
|
|
1437
|
+
var CssTokenParser = class {
|
|
1438
|
+
parse(cssString) {
|
|
1439
|
+
const entries = [];
|
|
1440
|
+
const themeMap = /* @__PURE__ */ new Map();
|
|
1441
|
+
let currentSet;
|
|
1442
|
+
let pendingThemeName;
|
|
1443
|
+
let currentThemeName;
|
|
1444
|
+
let insideRootBlock = false;
|
|
1445
|
+
const blockLines = [];
|
|
1446
|
+
for (const line of cssString.split(/\r?\n/g)) {
|
|
1447
|
+
const trimmed = line.trim();
|
|
1448
|
+
if (!insideRootBlock && trimmed.startsWith("/* Set:")) {
|
|
1449
|
+
currentSet = trimmed.replace("/* Set:", "").replace("*/", "").trim();
|
|
1450
|
+
pendingThemeName = currentSet;
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
if (!insideRootBlock && trimmed.startsWith("/* Modifier:")) {
|
|
1454
|
+
const modifierName = this.extractModifierThemeName(trimmed);
|
|
1455
|
+
pendingThemeName = modifierName ? currentSet ? `${currentSet}.${modifierName}` : modifierName : void 0;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (!insideRootBlock && trimmed.startsWith(":root") && trimmed.endsWith("{")) {
|
|
1459
|
+
insideRootBlock = true;
|
|
1460
|
+
currentThemeName = pendingThemeName ?? currentSet;
|
|
1461
|
+
blockLines.length = 0;
|
|
1462
|
+
pendingThemeName = void 0;
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
if (insideRootBlock) {
|
|
1466
|
+
if (trimmed === "}") {
|
|
1467
|
+
insideRootBlock = false;
|
|
1468
|
+
const blockEntries = this.extractBlockEntries(blockLines.join("\n"), currentThemeName);
|
|
1469
|
+
entries.push(...blockEntries);
|
|
1470
|
+
if (currentThemeName && blockEntries.length > 0) {
|
|
1471
|
+
if (!themeMap.has(currentThemeName)) themeMap.set(currentThemeName, []);
|
|
1472
|
+
themeMap.get(currentThemeName).push(...blockEntries);
|
|
1473
|
+
}
|
|
1474
|
+
currentThemeName = void 0;
|
|
1475
|
+
blockLines.length = 0;
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
blockLines.push(line);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return {
|
|
1482
|
+
entries,
|
|
1483
|
+
themes: [...themeMap.entries()].map(([name, themeEntries]) => ({
|
|
1484
|
+
name,
|
|
1485
|
+
entries: themeEntries
|
|
1486
|
+
}))
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
extractModifierThemeName(annotationValue) {
|
|
1490
|
+
return annotationValue.match(/theme\s*=\s*([a-zA-Z0-9_-]+)/)?.[1];
|
|
1491
|
+
}
|
|
1492
|
+
extractBlockEntries(body, themeName) {
|
|
1493
|
+
const entries = [];
|
|
1494
|
+
for (const match of body.matchAll(/--([^:]+):\s*([\s\S]*?);/g)) {
|
|
1495
|
+
const name = `--${match[1]}`;
|
|
1496
|
+
const value = match[2].replace(/\s+/g, " ").trim();
|
|
1497
|
+
const scope = match[1].split("-")[0] || "other";
|
|
1498
|
+
entries.push({
|
|
1499
|
+
name,
|
|
1500
|
+
value,
|
|
1501
|
+
scope,
|
|
1502
|
+
themeName
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
return entries;
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region src/core/showcase/TokenHtmlShowcaseStyles.ts
|
|
1510
|
+
var TOKEN_HTML_SHOWCASE_CSS = `
|
|
1511
|
+
*,
|
|
1512
|
+
*::before,
|
|
1513
|
+
*::after {
|
|
1514
|
+
box-sizing: border-box;
|
|
1515
|
+
margin: 0;
|
|
1516
|
+
padding: 0;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
html {
|
|
1520
|
+
scroll-padding-top: 112px;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
[id] {
|
|
1524
|
+
scroll-margin-top: 112px;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
body {
|
|
1528
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
1529
|
+
background: #f8fafc;
|
|
1530
|
+
color: #1e293b;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.page-header {
|
|
1534
|
+
position: sticky;
|
|
1535
|
+
top: 0;
|
|
1536
|
+
z-index: 20;
|
|
1537
|
+
height: 64px;
|
|
1538
|
+
display: flex;
|
|
1539
|
+
align-items: center;
|
|
1540
|
+
padding: 0 1.25rem;
|
|
1541
|
+
border-bottom: 1px solid #e2e8f0;
|
|
1542
|
+
background: #fff;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
.page-title {
|
|
1546
|
+
font-size: 1rem;
|
|
1547
|
+
font-weight: 700;
|
|
1548
|
+
letter-spacing: 0;
|
|
1549
|
+
text-transform: uppercase;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
.page-shell {
|
|
1553
|
+
display: grid;
|
|
1554
|
+
grid-template-columns: 240px 1fr;
|
|
1555
|
+
min-height: calc(100vh - 64px);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
.sidebar {
|
|
1559
|
+
border-right: 1px solid #e2e8f0;
|
|
1560
|
+
background: #fff;
|
|
1561
|
+
padding: 0.875rem 0.625rem;
|
|
1562
|
+
position: sticky;
|
|
1563
|
+
top: 64px;
|
|
1564
|
+
height: calc(100vh - 64px);
|
|
1565
|
+
overflow: auto;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.sidebar-title {
|
|
1569
|
+
font-size: 0.75rem;
|
|
1570
|
+
font-weight: 700;
|
|
1571
|
+
color: #64748b;
|
|
1572
|
+
text-transform: uppercase;
|
|
1573
|
+
margin: 0 0.625rem 0.75rem;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
.sidebar-nav {
|
|
1577
|
+
display: flex;
|
|
1578
|
+
flex-direction: column;
|
|
1579
|
+
gap: 0.125rem;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
.sidebar-group {
|
|
1583
|
+
display: flex;
|
|
1584
|
+
flex-direction: column;
|
|
1585
|
+
gap: 0.125rem;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
.sidebar-group-body {
|
|
1589
|
+
display: flex;
|
|
1590
|
+
flex-direction: column;
|
|
1591
|
+
gap: 0.125rem;
|
|
1592
|
+
padding-left: 0.75rem;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
.sidebar-group-body--scope {
|
|
1596
|
+
padding-left: 1rem;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
.sidebar-group-body[hidden] {
|
|
1600
|
+
display: none;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
.sidebar-link {
|
|
1604
|
+
display: flex;
|
|
1605
|
+
align-items: center;
|
|
1606
|
+
gap: 0.375rem;
|
|
1607
|
+
width: 100%;
|
|
1608
|
+
min-height: 28px;
|
|
1609
|
+
padding: 0.25rem 0.375rem;
|
|
1610
|
+
border-radius: 4px;
|
|
1611
|
+
text-decoration: none;
|
|
1612
|
+
color: #334155;
|
|
1613
|
+
font-size: 0.8125rem;
|
|
1614
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
1615
|
+
font-weight: 600;
|
|
1616
|
+
background: transparent;
|
|
1617
|
+
border: 0;
|
|
1618
|
+
text-align: left;
|
|
1619
|
+
cursor: pointer;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
.sidebar-link:hover {
|
|
1623
|
+
background: #f1f5f9;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.sidebar-toggle[aria-expanded="true"] {
|
|
1627
|
+
background: #e2e8f0;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.sidebar-chevron {
|
|
1631
|
+
width: 0.3125rem;
|
|
1632
|
+
height: 0.3125rem;
|
|
1633
|
+
border-right: 1.25px solid #94a3b8;
|
|
1634
|
+
border-bottom: 1.25px solid #94a3b8;
|
|
1635
|
+
transform: rotate(-45deg);
|
|
1636
|
+
transition: transform 0.12s ease;
|
|
1637
|
+
flex-shrink: 0;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
.sidebar-toggle[aria-expanded="true"] .sidebar-chevron {
|
|
1641
|
+
transform: rotate(45deg);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
.lucide-folder {
|
|
1645
|
+
width: 1rem;
|
|
1646
|
+
height: 1rem;
|
|
1647
|
+
color: #64748b;
|
|
1648
|
+
flex-shrink: 0;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
.sidebar-label {
|
|
1652
|
+
min-width: 0;
|
|
1653
|
+
overflow: hidden;
|
|
1654
|
+
text-overflow: ellipsis;
|
|
1655
|
+
white-space: nowrap;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
.sidebar-sub-link {
|
|
1659
|
+
display: block;
|
|
1660
|
+
padding: 0.25rem 0.375rem 0.25rem 1rem;
|
|
1661
|
+
border-radius: 4px;
|
|
1662
|
+
text-decoration: none;
|
|
1663
|
+
color: #64748b;
|
|
1664
|
+
font-size: 0.75rem;
|
|
1665
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
1666
|
+
line-height: 1.35;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
.sidebar-sub-link:hover {
|
|
1670
|
+
background: #f1f5f9;
|
|
1671
|
+
color: #334155;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
.sidebar-sub-label {
|
|
1675
|
+
display: block;
|
|
1676
|
+
padding: 0.375rem 0.625rem 0.125rem;
|
|
1677
|
+
color: #334155;
|
|
1678
|
+
font-size: 0.75rem;
|
|
1679
|
+
font-weight: 700;
|
|
1680
|
+
font-family: "JetBrains Mono", monospace;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
.sidebar-sub-link--nested {
|
|
1684
|
+
padding-left: 2rem;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
.sidebar-sub-label--nested {
|
|
1688
|
+
padding-left: 2rem;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
.sidebar-sub-link--deep {
|
|
1692
|
+
padding-left: 2.75rem;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
.content {
|
|
1696
|
+
padding: 1.5rem 1.5rem 2rem;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
.theme-summary {
|
|
1700
|
+
margin-bottom: 1.5rem;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.theme-summary-title {
|
|
1704
|
+
font-size: 0.75rem;
|
|
1705
|
+
color: #64748b;
|
|
1706
|
+
text-transform: uppercase;
|
|
1707
|
+
margin-bottom: 0.5rem;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
.theme-summary-grid {
|
|
1711
|
+
display: grid;
|
|
1712
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
1713
|
+
gap: 0.5rem;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
.theme-summary-card {
|
|
1717
|
+
border: 1px solid #e2e8f0;
|
|
1718
|
+
border-radius: 10px;
|
|
1719
|
+
background: #fff;
|
|
1720
|
+
padding: 0.625rem;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.theme-summary-header {
|
|
1724
|
+
display: flex;
|
|
1725
|
+
align-items: baseline;
|
|
1726
|
+
gap: 0.375rem;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
.theme-summary-label {
|
|
1730
|
+
color: #64748b;
|
|
1731
|
+
font-size: 0.6875rem;
|
|
1732
|
+
letter-spacing: 0.04em;
|
|
1733
|
+
text-transform: uppercase;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.theme-summary-name {
|
|
1737
|
+
color: #0f172a;
|
|
1738
|
+
font-family: "JetBrains Mono", monospace;
|
|
1739
|
+
font-size: 0.875rem;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
.theme-summary-stats {
|
|
1743
|
+
display: grid;
|
|
1744
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1745
|
+
gap: 0.375rem;
|
|
1746
|
+
margin: 0.5rem 0 0;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
.theme-summary-stats div {
|
|
1750
|
+
border-radius: 8px;
|
|
1751
|
+
background: #f8fafc;
|
|
1752
|
+
padding: 0.375rem;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
.theme-summary-stats dt {
|
|
1756
|
+
color: #64748b;
|
|
1757
|
+
font-size: 0.6875rem;
|
|
1758
|
+
text-transform: uppercase;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
.theme-summary-stats dd {
|
|
1762
|
+
margin: 0.25rem 0 0;
|
|
1763
|
+
color: #0f172a;
|
|
1764
|
+
font-size: 0.9375rem;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
.back-to-top {
|
|
1768
|
+
position: fixed;
|
|
1769
|
+
right: 1.25rem;
|
|
1770
|
+
bottom: 1.25rem;
|
|
1771
|
+
z-index: 40;
|
|
1772
|
+
display: inline-flex;
|
|
1773
|
+
align-items: center;
|
|
1774
|
+
justify-content: center;
|
|
1775
|
+
width: 40px;
|
|
1776
|
+
height: 40px;
|
|
1777
|
+
border: 1px solid #e2e8f0;
|
|
1778
|
+
border-radius: 999px;
|
|
1779
|
+
color: #334155;
|
|
1780
|
+
background: #fff;
|
|
1781
|
+
cursor: pointer;
|
|
1782
|
+
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
.back-to-top:hover {
|
|
1786
|
+
background: #f8fafc;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
.token-category {
|
|
1790
|
+
font-size: 1.25rem;
|
|
1791
|
+
font-weight: 600;
|
|
1792
|
+
margin: 0 0 1rem;
|
|
1793
|
+
padding-bottom: 0.5rem;
|
|
1794
|
+
text-transform: capitalize;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
.token-section {
|
|
1798
|
+
margin-bottom: 2rem;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
.token-section--theme-scope {
|
|
1802
|
+
margin-bottom: 1.5rem;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
.theme-section {
|
|
1806
|
+
margin-bottom: 2rem;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
.theme-title {
|
|
1810
|
+
font-size: 1.4rem;
|
|
1811
|
+
font-weight: 400;
|
|
1812
|
+
color: #334155;
|
|
1813
|
+
margin: 0 0 1rem;
|
|
1814
|
+
padding-bottom: 0.5rem;
|
|
1815
|
+
border-bottom: 1px solid #e2e8f0;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
.token-subgroup-title {
|
|
1819
|
+
font-size: 0.875rem;
|
|
1820
|
+
font-weight: 700;
|
|
1821
|
+
color: #64748b;
|
|
1822
|
+
text-transform: uppercase;
|
|
1823
|
+
margin: 0 0 0.625rem;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
.token-subgroup {
|
|
1827
|
+
margin-bottom: 1.5rem;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
.token-list {
|
|
1831
|
+
display: grid;
|
|
1832
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
1833
|
+
gap: 0.75rem;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
.token-list.token-list--font {
|
|
1837
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
.token-item {
|
|
1841
|
+
display: flex;
|
|
1842
|
+
flex-direction: column;
|
|
1843
|
+
align-items: flex-start;
|
|
1844
|
+
gap: 0.5rem;
|
|
1845
|
+
min-height: 130px;
|
|
1846
|
+
background: #fff;
|
|
1847
|
+
border: 1px solid #e2e8f0;
|
|
1848
|
+
border-radius: 8px;
|
|
1849
|
+
padding: 0.75rem 0.875rem;
|
|
1850
|
+
font-family: "JetBrains Mono", monospace;
|
|
1851
|
+
font-size: 0.8125rem;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
.token-swatch {
|
|
1855
|
+
width: 100%;
|
|
1856
|
+
height: 56px;
|
|
1857
|
+
border-radius: 6px;
|
|
1858
|
+
border: 1px solid #e2e8f0;
|
|
1859
|
+
flex-shrink: 0;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
.token-item--color-card {
|
|
1863
|
+
position: relative;
|
|
1864
|
+
gap: 0;
|
|
1865
|
+
min-height: 205px;
|
|
1866
|
+
padding: 0.5rem;
|
|
1867
|
+
border-radius: 6px;
|
|
1868
|
+
overflow: hidden;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
.token-item--color-card .token-swatch {
|
|
1872
|
+
height: 109px;
|
|
1873
|
+
margin-bottom: 0.875rem;
|
|
1874
|
+
border: 0;
|
|
1875
|
+
border-radius: 4px;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
.token-item--color-card .token-type-badge {
|
|
1879
|
+
position: absolute;
|
|
1880
|
+
top: 1.375rem;
|
|
1881
|
+
left: 1.4375rem;
|
|
1882
|
+
padding: 0.25rem 0.5rem;
|
|
1883
|
+
border: 0;
|
|
1884
|
+
border-radius: 3px;
|
|
1885
|
+
background: #ffffff;
|
|
1886
|
+
color: #8b5cf6;
|
|
1887
|
+
font-size: 0.5625rem;
|
|
1888
|
+
font-weight: 700;
|
|
1889
|
+
letter-spacing: 0.14em;
|
|
1890
|
+
line-height: 1;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
.token-item--color-card .token-name {
|
|
1894
|
+
width: 100%;
|
|
1895
|
+
margin: 0 0 auto;
|
|
1896
|
+
padding: 0 0.875rem 0 0;
|
|
1897
|
+
color: #334155;
|
|
1898
|
+
font-size: 0.875rem;
|
|
1899
|
+
line-height: 1.45;
|
|
1900
|
+
min-height: 0;
|
|
1901
|
+
word-break: normal;
|
|
1902
|
+
overflow-wrap: anywhere;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
.token-item--color-card .token-meta {
|
|
1906
|
+
padding: 0 0.875rem 0.375rem 0;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
.token-item--color-card .token-meta b {
|
|
1910
|
+
color: #334155;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
.token-name {
|
|
1914
|
+
color: #475569;
|
|
1915
|
+
min-width: 0;
|
|
1916
|
+
word-break: break-all;
|
|
1917
|
+
font-size: 14px;
|
|
1918
|
+
margin-bottom: 0.25rem;
|
|
1919
|
+
min-height: 34px;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
.font-preview {
|
|
1923
|
+
width: 100%;
|
|
1924
|
+
border: 1px solid #e2e8f0;
|
|
1925
|
+
border-radius: 6px;
|
|
1926
|
+
padding: 0.7rem;
|
|
1927
|
+
background: #f8fafc;
|
|
1928
|
+
height: 128px;
|
|
1929
|
+
overflow: hidden;
|
|
1930
|
+
display: -webkit-box;
|
|
1931
|
+
-webkit-box-orient: vertical;
|
|
1932
|
+
-webkit-line-clamp: 6;
|
|
1933
|
+
text-overflow: ellipsis;
|
|
1934
|
+
overflow-wrap: anywhere;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
.font-preview--grid {
|
|
1938
|
+
background-color: #fcfcfd;
|
|
1939
|
+
background-image:
|
|
1940
|
+
linear-gradient(to right, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
1941
|
+
linear-gradient(to bottom, rgba(148, 163, 184, 0.18) 1px, transparent 1px);
|
|
1942
|
+
background-size: 12px 12px;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
.token-item--font-card {
|
|
1946
|
+
min-height: 0;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
.token-name--font-card {
|
|
1950
|
+
min-height: 0;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
.token-meta {
|
|
1954
|
+
display: block;
|
|
1955
|
+
width: 100%;
|
|
1956
|
+
color: #64748b;
|
|
1957
|
+
font-size: 0.6875rem;
|
|
1958
|
+
line-height: 1.35;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
.token-meta span {
|
|
1962
|
+
display: flex;
|
|
1963
|
+
align-items: flex-start;
|
|
1964
|
+
gap: 0.25rem;
|
|
1965
|
+
min-width: 0;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
.token-meta b {
|
|
1969
|
+
color: #334155;
|
|
1970
|
+
font-weight: 700;
|
|
1971
|
+
margin-right: 0;
|
|
1972
|
+
flex-shrink: 0;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
.token-meta-value {
|
|
1976
|
+
color: #64748b;
|
|
1977
|
+
background: transparent;
|
|
1978
|
+
border-radius: 0;
|
|
1979
|
+
padding: 0;
|
|
1980
|
+
display: inline-block;
|
|
1981
|
+
min-width: 0;
|
|
1982
|
+
overflow-wrap: anywhere;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
.spacing-visual {
|
|
1986
|
+
display: flex;
|
|
1987
|
+
align-items: center;
|
|
1988
|
+
gap: 0.5rem;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
.spacing-bar {
|
|
1992
|
+
height: 1rem;
|
|
1993
|
+
background: #3b82f6;
|
|
1994
|
+
border-radius: 4px;
|
|
1995
|
+
flex-shrink: 0;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
.radius-visual {
|
|
1999
|
+
width: 3rem;
|
|
2000
|
+
height: 3rem;
|
|
2001
|
+
background: #e2e8f0;
|
|
2002
|
+
border: 1px dashed #94a3b8;
|
|
2003
|
+
flex-shrink: 0;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
.token-item--radius-card {
|
|
2007
|
+
min-height: 0;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
.radius-preview {
|
|
2011
|
+
width: 100%;
|
|
2012
|
+
height: 112px;
|
|
2013
|
+
border: 1px solid #e2e8f0;
|
|
2014
|
+
border-radius: 6px;
|
|
2015
|
+
padding: 1.75rem;
|
|
2016
|
+
background: #fcfcfd;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
.radius-preview--grid {
|
|
2020
|
+
background-image:
|
|
2021
|
+
linear-gradient(to right, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
2022
|
+
linear-gradient(to bottom, rgba(148, 163, 184, 0.18) 1px, transparent 1px);
|
|
2023
|
+
background-size: 12px 12px;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
.radius-preview-box {
|
|
2027
|
+
width: 100%;
|
|
2028
|
+
height: 100%;
|
|
2029
|
+
border: 2px solid #334155;
|
|
2030
|
+
background: rgba(255, 255, 255, 0.7);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
.token-item--opacity-card {
|
|
2034
|
+
min-height: 0;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
.opacity-preview {
|
|
2038
|
+
width: 100%;
|
|
2039
|
+
height: 112px;
|
|
2040
|
+
border: 1px solid #e2e8f0;
|
|
2041
|
+
border-radius: 6px;
|
|
2042
|
+
padding: 1.75rem;
|
|
2043
|
+
background-color: #fcfcfd;
|
|
2044
|
+
overflow: hidden;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
.opacity-preview--checker {
|
|
2048
|
+
background-image:
|
|
2049
|
+
linear-gradient(45deg, rgba(148, 163, 184, 0.22) 25%, transparent 25%),
|
|
2050
|
+
linear-gradient(-45deg, rgba(148, 163, 184, 0.22) 25%, transparent 25%),
|
|
2051
|
+
linear-gradient(45deg, transparent 75%, rgba(148, 163, 184, 0.22) 75%),
|
|
2052
|
+
linear-gradient(-45deg, transparent 75%, rgba(148, 163, 184, 0.22) 75%);
|
|
2053
|
+
background-position:
|
|
2054
|
+
0 0,
|
|
2055
|
+
0 6px,
|
|
2056
|
+
6px -6px,
|
|
2057
|
+
-6px 0;
|
|
2058
|
+
background-size: 12px 12px;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
.opacity-preview-fill {
|
|
2062
|
+
width: 100%;
|
|
2063
|
+
height: 100%;
|
|
2064
|
+
border-radius: 4px;
|
|
2065
|
+
background: #334155;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
.font-sample {
|
|
2069
|
+
font-size: 1.125rem;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
.shadow-visual {
|
|
2073
|
+
width: 4rem;
|
|
2074
|
+
height: 4rem;
|
|
2075
|
+
background: #fff;
|
|
2076
|
+
border-radius: 4px;
|
|
2077
|
+
flex-shrink: 0;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
.token-item--shadow-card {
|
|
2081
|
+
min-height: 0;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
.shadow-preview {
|
|
2085
|
+
width: 100%;
|
|
2086
|
+
height: 112px;
|
|
2087
|
+
border: 1px solid #e2e8f0;
|
|
2088
|
+
border-radius: 6px;
|
|
2089
|
+
padding: 1.75rem;
|
|
2090
|
+
background: #fcfcfd;
|
|
2091
|
+
overflow: visible;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
.shadow-preview--grid {
|
|
2095
|
+
background-image:
|
|
2096
|
+
linear-gradient(to right, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
2097
|
+
linear-gradient(to bottom, rgba(148, 163, 184, 0.18) 1px, transparent 1px);
|
|
2098
|
+
background-size: 12px 12px;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
.shadow-preview-box {
|
|
2102
|
+
width: 100%;
|
|
2103
|
+
height: 100%;
|
|
2104
|
+
background: rgba(255, 255, 255, 0.92);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
.shadow-layer {
|
|
2108
|
+
display: block;
|
|
2109
|
+
color: #64748b;
|
|
2110
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
2111
|
+
line-height: 1.5;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
.token-item--motion-card {
|
|
2115
|
+
min-height: 0;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
.motion-preview {
|
|
2119
|
+
width: 100%;
|
|
2120
|
+
height: 112px;
|
|
2121
|
+
border: 1px solid #e2e8f0;
|
|
2122
|
+
border-radius: 6px;
|
|
2123
|
+
padding: 1.75rem;
|
|
2124
|
+
background: #fcfcfd;
|
|
2125
|
+
overflow: hidden;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
.motion-preview--grid {
|
|
2129
|
+
background-image:
|
|
2130
|
+
linear-gradient(to right, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
2131
|
+
linear-gradient(to bottom, rgba(148, 163, 184, 0.18) 1px, transparent 1px);
|
|
2132
|
+
background-size: 12px 12px;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
.motion-track {
|
|
2136
|
+
position: relative;
|
|
2137
|
+
width: 100%;
|
|
2138
|
+
height: 100%;
|
|
2139
|
+
border-bottom: 2px solid rgba(51, 65, 85, 0.2);
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
.motion-dot {
|
|
2143
|
+
position: absolute;
|
|
2144
|
+
left: 0;
|
|
2145
|
+
bottom: -7px;
|
|
2146
|
+
width: 14px;
|
|
2147
|
+
height: 14px;
|
|
2148
|
+
border-radius: 999px;
|
|
2149
|
+
background: #334155;
|
|
2150
|
+
box-shadow: 0 0 0 4px rgba(44, 77, 243, 0.18);
|
|
2151
|
+
animation: token-motion-move var(--motion-duration, 1200ms) var(--motion-timing, ease) var(--motion-delay, 0ms) infinite alternate;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
@keyframes token-motion-move {
|
|
2155
|
+
from {
|
|
2156
|
+
left: 0;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
to {
|
|
2160
|
+
left: calc(100% - 14px);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
.border-visual {
|
|
2165
|
+
width: 4rem;
|
|
2166
|
+
height: 2.5rem;
|
|
2167
|
+
flex-shrink: 0;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
.token-item--border-card {
|
|
2171
|
+
min-height: 0;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
.border-preview {
|
|
2175
|
+
width: 100%;
|
|
2176
|
+
height: 112px;
|
|
2177
|
+
border: 1px solid #e2e8f0;
|
|
2178
|
+
border-radius: 6px;
|
|
2179
|
+
padding: 1.75rem;
|
|
2180
|
+
background: #fcfcfd;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
.border-preview--grid {
|
|
2184
|
+
background-image:
|
|
2185
|
+
linear-gradient(to right, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
2186
|
+
linear-gradient(to bottom, rgba(148, 163, 184, 0.18) 1px, transparent 1px);
|
|
2187
|
+
background-size: 12px 12px;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
.border-preview-box {
|
|
2191
|
+
width: 100%;
|
|
2192
|
+
height: 100%;
|
|
2193
|
+
background: rgba(255, 255, 255, 0.7);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
.token-item--dimension-card {
|
|
2197
|
+
min-height: 0;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
.dimension-preview {
|
|
2201
|
+
position: relative;
|
|
2202
|
+
width: 100%;
|
|
2203
|
+
height: 112px;
|
|
2204
|
+
border: 1px solid #e2e8f0;
|
|
2205
|
+
border-radius: 6px;
|
|
2206
|
+
background: #fcfcfd;
|
|
2207
|
+
overflow: hidden;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
.dimension-preview--grid {
|
|
2211
|
+
background-image:
|
|
2212
|
+
linear-gradient(to right, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
2213
|
+
linear-gradient(to bottom, rgba(148, 163, 184, 0.18) 1px, transparent 1px);
|
|
2214
|
+
background-size: 12px 12px;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
.dimension-marker {
|
|
2218
|
+
position: absolute;
|
|
2219
|
+
top: 0;
|
|
2220
|
+
bottom: 0;
|
|
2221
|
+
left: 50%;
|
|
2222
|
+
min-width: 1px;
|
|
2223
|
+
max-width: 100%;
|
|
2224
|
+
background: #334155;
|
|
2225
|
+
transform: translateX(-50%);
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
.token-meta--stack {
|
|
2229
|
+
display: flex;
|
|
2230
|
+
flex-direction: column;
|
|
2231
|
+
gap: 0.125rem;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
.gradient-visual {
|
|
2235
|
+
width: 6rem;
|
|
2236
|
+
height: 2.5rem;
|
|
2237
|
+
border-radius: 4px;
|
|
2238
|
+
flex-shrink: 0;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
.token-item--gradient-card {
|
|
2242
|
+
min-height: 0;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
.token-item--gradient-card .token-swatch {
|
|
2246
|
+
height: 109px;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
.gradient-stops {
|
|
2250
|
+
display: grid;
|
|
2251
|
+
grid-template-columns: 1fr;
|
|
2252
|
+
gap: 0.375rem;
|
|
2253
|
+
width: 100%;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
.gradient-stop {
|
|
2257
|
+
display: grid;
|
|
2258
|
+
grid-template-columns: 48px minmax(0, 1fr);
|
|
2259
|
+
gap: 0.5rem;
|
|
2260
|
+
align-items: center;
|
|
2261
|
+
min-width: 0;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
.gradient-stop__swatch.token-swatch {
|
|
2265
|
+
width: 48px;
|
|
2266
|
+
height: 28px;
|
|
2267
|
+
border-radius: 4px;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
.gradient-stop__value {
|
|
2271
|
+
min-width: 0;
|
|
2272
|
+
color: #64748b;
|
|
2273
|
+
font-size: 0.6875rem;
|
|
2274
|
+
overflow-wrap: anywhere;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
.token-type-badge {
|
|
2278
|
+
font-size: 0.625rem;
|
|
2279
|
+
font-weight: 600;
|
|
2280
|
+
text-transform: uppercase;
|
|
2281
|
+
letter-spacing: 0.05em;
|
|
2282
|
+
color: #94a3b8;
|
|
2283
|
+
border: 1px solid #e2e8f0;
|
|
2284
|
+
border-radius: 4px;
|
|
2285
|
+
padding: 0.125rem 0.375rem;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
.semantic-section {
|
|
2289
|
+
display: flex;
|
|
2290
|
+
flex-direction: column;
|
|
2291
|
+
gap: 1.25rem;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
.section-header {
|
|
2295
|
+
padding-bottom: 0.75rem;
|
|
2296
|
+
border-bottom: 1px solid #e2e8f0;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
.section-header h2 {
|
|
2300
|
+
font-size: 1.25rem;
|
|
2301
|
+
font-weight: 600;
|
|
2302
|
+
margin-bottom: 0.375rem;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
.section-header p {
|
|
2306
|
+
max-width: 680px;
|
|
2307
|
+
color: #64748b;
|
|
2308
|
+
font-size: 0.875rem;
|
|
2309
|
+
line-height: 1.5;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
.semantic-groups {
|
|
2313
|
+
display: flex;
|
|
2314
|
+
flex-direction: column;
|
|
2315
|
+
gap: 1.25rem;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
.semantic-group {
|
|
2319
|
+
display: grid;
|
|
2320
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
2321
|
+
gap: 0.75rem;
|
|
2322
|
+
min-width: 0;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
.semantic-group h3 {
|
|
2326
|
+
grid-column: 1 / -1;
|
|
2327
|
+
margin-bottom: 0.5rem;
|
|
2328
|
+
color: #64748b;
|
|
2329
|
+
font-size: 0.8125rem;
|
|
2330
|
+
font-weight: 700;
|
|
2331
|
+
text-transform: uppercase;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
.semantic-role {
|
|
2335
|
+
display: grid;
|
|
2336
|
+
grid-template-columns: 28px minmax(0, 1fr);
|
|
2337
|
+
gap: 0.75rem;
|
|
2338
|
+
align-items: flex-start;
|
|
2339
|
+
min-height: 0;
|
|
2340
|
+
padding: 0.75rem;
|
|
2341
|
+
border: 1px solid #e2e8f0;
|
|
2342
|
+
border-radius: 8px;
|
|
2343
|
+
background: #fff;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
.semantic-role--plain {
|
|
2347
|
+
grid-template-columns: 1fr;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
.semantic-role__content {
|
|
2351
|
+
display: grid;
|
|
2352
|
+
grid-template-columns: 1fr;
|
|
2353
|
+
gap: 0.375rem;
|
|
2354
|
+
align-items: start;
|
|
2355
|
+
min-width: 0;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
.semantic-role__header {
|
|
2359
|
+
display: flex;
|
|
2360
|
+
flex-wrap: wrap;
|
|
2361
|
+
align-items: center;
|
|
2362
|
+
gap: 0.375rem;
|
|
2363
|
+
min-width: 0;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
.semantic-role__name {
|
|
2367
|
+
color: #1e293b;
|
|
2368
|
+
font-size: 0.875rem;
|
|
2369
|
+
font-weight: 700;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
.semantic-role__token {
|
|
2373
|
+
display: block;
|
|
2374
|
+
color: #64748b;
|
|
2375
|
+
font-family: "JetBrains Mono", monospace;
|
|
2376
|
+
font-size: 0.75rem;
|
|
2377
|
+
overflow-wrap: anywhere;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
.semantic-role__meta {
|
|
2381
|
+
display: flex;
|
|
2382
|
+
flex-wrap: nowrap;
|
|
2383
|
+
gap: 0.375rem;
|
|
2384
|
+
color: #64748b;
|
|
2385
|
+
font-size: 0.75rem;
|
|
2386
|
+
line-height: 1.4;
|
|
2387
|
+
min-width: 0;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
.semantic-role__meta span {
|
|
2391
|
+
flex-shrink: 0;
|
|
2392
|
+
color: #334155;
|
|
2393
|
+
font-weight: 700;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
.semantic-role__meta code {
|
|
2397
|
+
color: #64748b;
|
|
2398
|
+
font-family: "JetBrains Mono", monospace;
|
|
2399
|
+
overflow-wrap: anywhere;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
.semantic-role__meta--resolved code {
|
|
2403
|
+
min-width: 0;
|
|
2404
|
+
overflow: hidden;
|
|
2405
|
+
text-overflow: ellipsis;
|
|
2406
|
+
white-space: nowrap;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
.semantic-role__parts {
|
|
2410
|
+
display: flex;
|
|
2411
|
+
flex-direction: column;
|
|
2412
|
+
gap: 0.25rem;
|
|
2413
|
+
color: #64748b;
|
|
2414
|
+
font-family: "JetBrains Mono", monospace;
|
|
2415
|
+
font-size: 0.75rem;
|
|
2416
|
+
line-height: 1.35;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
.semantic-role__parts-label {
|
|
2420
|
+
color: #334155;
|
|
2421
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
2422
|
+
font-weight: 700;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
.semantic-role__parts span:not(.semantic-role__parts-label) {
|
|
2426
|
+
display: grid;
|
|
2427
|
+
grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
|
|
2428
|
+
gap: 0.375rem;
|
|
2429
|
+
min-width: 0;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
.semantic-role__parts span:not(.semantic-role__parts-label) code:not(:last-child)::after {
|
|
2433
|
+
content: ":";
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
.semantic-role__parts code {
|
|
2437
|
+
min-width: 0;
|
|
2438
|
+
overflow-wrap: anywhere;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
.semantic-role__ref {
|
|
2442
|
+
color: #2c4df3;
|
|
2443
|
+
text-decoration: none;
|
|
2444
|
+
min-width: 0;
|
|
2445
|
+
overflow: hidden;
|
|
2446
|
+
text-overflow: ellipsis;
|
|
2447
|
+
white-space: nowrap;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
.semantic-role__ref:hover {
|
|
2451
|
+
text-decoration: underline;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
.semantic-role__warning {
|
|
2455
|
+
display: inline-flex;
|
|
2456
|
+
align-items: center;
|
|
2457
|
+
min-height: 18px;
|
|
2458
|
+
padding: 0.125rem 0.375rem;
|
|
2459
|
+
border-radius: 999px;
|
|
2460
|
+
background: #fff7ed;
|
|
2461
|
+
color: #9a3412;
|
|
2462
|
+
font-size: 0.6875rem;
|
|
2463
|
+
font-weight: 700;
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
.semantic-role__swatch,
|
|
2467
|
+
.semantic-role__marker,
|
|
2468
|
+
.semantic-role__space,
|
|
2469
|
+
.semantic-role__radius,
|
|
2470
|
+
.semantic-role__text-preview {
|
|
2471
|
+
width: 28px;
|
|
2472
|
+
height: 28px;
|
|
2473
|
+
margin-top: 0.125rem;
|
|
2474
|
+
border: 1px solid #e2e8f0;
|
|
2475
|
+
border-radius: 6px;
|
|
2476
|
+
background: #fff;
|
|
2477
|
+
flex-shrink: 0;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
.semantic-role__swatch--empty {
|
|
2481
|
+
background:
|
|
2482
|
+
linear-gradient(45deg, transparent 45%, #cbd5e1 45%, #cbd5e1 55%, transparent 55%),
|
|
2483
|
+
#f8fafc;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
.semantic-role__marker {
|
|
2487
|
+
background: #f8fafc;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
.semantic-role__space,
|
|
2491
|
+
.semantic-role__radius {
|
|
2492
|
+
position: relative;
|
|
2493
|
+
overflow: hidden;
|
|
2494
|
+
background:
|
|
2495
|
+
linear-gradient(to right, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
2496
|
+
linear-gradient(to bottom, rgba(148, 163, 184, 0.18) 1px, transparent 1px),
|
|
2497
|
+
#fcfcfd;
|
|
2498
|
+
background-size: 7px 7px;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
.semantic-role__space span {
|
|
2502
|
+
position: absolute;
|
|
2503
|
+
top: 7px;
|
|
2504
|
+
bottom: 7px;
|
|
2505
|
+
left: 4px;
|
|
2506
|
+
min-width: 1px;
|
|
2507
|
+
max-width: 20px;
|
|
2508
|
+
background: #334155;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
.semantic-role__radius span {
|
|
2512
|
+
position: absolute;
|
|
2513
|
+
inset: 6px;
|
|
2514
|
+
border: 2px solid #334155;
|
|
2515
|
+
background: rgba(255, 255, 255, 0.7);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
.semantic-role__text-preview {
|
|
2519
|
+
display: inline-flex;
|
|
2520
|
+
align-items: center;
|
|
2521
|
+
justify-content: center;
|
|
2522
|
+
color: #334155;
|
|
2523
|
+
font-size: 0.875rem;
|
|
2524
|
+
overflow: hidden;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
@media (max-width: 1480px) {
|
|
2528
|
+
.token-list {
|
|
2529
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
@media (max-width: 1200px) {
|
|
2534
|
+
.token-list {
|
|
2535
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
.token-list.token-list--font {
|
|
2539
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
.semantic-group {
|
|
2543
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
@media (max-width: 1100px) {
|
|
2549
|
+
.semantic-group {
|
|
2550
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
@media (max-width: 900px) {
|
|
2555
|
+
.page-shell {
|
|
2556
|
+
grid-template-columns: 1fr;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
.sidebar {
|
|
2560
|
+
position: static;
|
|
2561
|
+
height: auto;
|
|
2562
|
+
border-right: 0;
|
|
2563
|
+
border-bottom: 1px solid #e2e8f0;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
.token-list {
|
|
2567
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
.token-list.token-list--font {
|
|
2571
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
.content {
|
|
2575
|
+
padding: 1rem;
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
.token-item {
|
|
2579
|
+
padding: 0.625rem 0.75rem;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
.token-name {
|
|
2583
|
+
min-width: 0;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
@media (max-width: 560px) {
|
|
2588
|
+
.token-list {
|
|
2589
|
+
grid-template-columns: 1fr;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
.token-list.token-list--font {
|
|
2593
|
+
grid-template-columns: 1fr;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
.semantic-group {
|
|
2597
|
+
grid-template-columns: 1fr;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
.semantic-role {
|
|
2601
|
+
align-items: flex-start;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
.semantic-role__content {
|
|
2605
|
+
grid-template-columns: 1fr;
|
|
2606
|
+
gap: 0.25rem;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
`;
|
|
2612
|
+
//#endregion
|
|
2613
|
+
//#region src/core/showcase/TokenHtmlShowcaseAggregators.ts
|
|
2614
|
+
/**
|
|
2615
|
+
* Groups typography tokens into models for the HTML showcase.
|
|
2616
|
+
*
|
|
2617
|
+
* @remarks
|
|
2618
|
+
* Combines separate CSS variables of one text style:
|
|
2619
|
+
* font size, font weight, line height, letter spacing and font family.
|
|
2620
|
+
*
|
|
2621
|
+
* The result is used by the renderer to show one typography card
|
|
2622
|
+
* instead of many separate CSS variables.
|
|
2623
|
+
*/
|
|
2624
|
+
var TypographyTokenAggregator = class {
|
|
2625
|
+
aggregate(tokens) {
|
|
2626
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2627
|
+
const result = [];
|
|
2628
|
+
for (const token of tokens) {
|
|
2629
|
+
const aggregateKey = this.getTypographyAggregateKey(token.name);
|
|
2630
|
+
if (!aggregateKey) {
|
|
2631
|
+
result.push({
|
|
2632
|
+
name: token.name,
|
|
2633
|
+
value: token.value
|
|
2634
|
+
});
|
|
2635
|
+
continue;
|
|
2636
|
+
}
|
|
2637
|
+
let group = groups.get(aggregateKey.key);
|
|
2638
|
+
if (!group) {
|
|
2639
|
+
group = {
|
|
2640
|
+
name: aggregateKey.key,
|
|
2641
|
+
typography: {}
|
|
2642
|
+
};
|
|
2643
|
+
groups.set(aggregateKey.key, group);
|
|
2644
|
+
}
|
|
2645
|
+
this.assignTypographyProperty(group.typography, aggregateKey.property, token.value);
|
|
2646
|
+
}
|
|
2647
|
+
for (const group of groups.values()) result.push({
|
|
2648
|
+
name: `--${group.name}`,
|
|
2649
|
+
value: this.formatTypographyValue(group.typography),
|
|
2650
|
+
typography: group.typography
|
|
2651
|
+
});
|
|
2652
|
+
return result.sort((left, right) => left.name.localeCompare(right.name));
|
|
2653
|
+
}
|
|
2654
|
+
getTypographyAggregateKey(name) {
|
|
2655
|
+
const normalized = name.replace(/^--/, "");
|
|
2656
|
+
for (const pattern of [
|
|
2657
|
+
/^(.*)-fontSize$/,
|
|
2658
|
+
/^(.*)-fontWeight$/,
|
|
2659
|
+
/^(.*)-letterSpacing$/,
|
|
2660
|
+
/^(.*)-lineHeight$/,
|
|
2661
|
+
/^(.*)-fontFamily-\d+$/
|
|
2662
|
+
]) {
|
|
2663
|
+
const match = normalized.match(pattern);
|
|
2664
|
+
if (!match) continue;
|
|
2665
|
+
const property = normalized.slice(match[1].length + 1);
|
|
2666
|
+
return {
|
|
2667
|
+
key: match[1],
|
|
2668
|
+
property
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
return null;
|
|
2672
|
+
}
|
|
2673
|
+
assignTypographyProperty(info, property, value) {
|
|
2674
|
+
if (property === "fontSize") {
|
|
2675
|
+
info.size = value;
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
if (property === "fontWeight") {
|
|
2679
|
+
info.weight = value;
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
if (property === "letterSpacing") {
|
|
2683
|
+
info.letterSpacing = value;
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
if (property === "lineHeight") {
|
|
2687
|
+
info.lineHeight = value;
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
if (property.startsWith("fontFamily-")) info.family = info.family ? `${info.family}, ${value}` : value;
|
|
2691
|
+
}
|
|
2692
|
+
formatTypographyValue(info) {
|
|
2693
|
+
const parts = [];
|
|
2694
|
+
if (info.weight) parts.push(info.weight);
|
|
2695
|
+
if (info.size) parts.push(info.lineHeight ? `${info.size}/${info.lineHeight}` : info.size);
|
|
2696
|
+
if (info.family) parts.push(info.family);
|
|
2697
|
+
if (parts.length === 0) return "";
|
|
2698
|
+
if (info.letterSpacing) return `${parts.join(" ")}; letter-spacing ${info.letterSpacing}`;
|
|
2699
|
+
return parts.join(" ");
|
|
2700
|
+
}
|
|
2701
|
+
};
|
|
2702
|
+
/**
|
|
2703
|
+
* Groups font tokens into collections for the HTML showcase.
|
|
2704
|
+
*
|
|
2705
|
+
* @remarks
|
|
2706
|
+
* Sorts font family, font weight, font size, line height
|
|
2707
|
+
* and letter spacing into separate sections with a fixed order.
|
|
2708
|
+
*/
|
|
2709
|
+
var FontCollectionAggregator = class {
|
|
2710
|
+
aggregate(tokens) {
|
|
2711
|
+
const titles = new Map([
|
|
2712
|
+
["font-family", "font family"],
|
|
2713
|
+
["font-weight", "font weight"],
|
|
2714
|
+
["font-size", "font size"],
|
|
2715
|
+
["line-height", "line height"],
|
|
2716
|
+
["letter-spacing", "letter spacing"]
|
|
2717
|
+
]);
|
|
2718
|
+
const order = [
|
|
2719
|
+
"font-family",
|
|
2720
|
+
"font-weight",
|
|
2721
|
+
"font-size",
|
|
2722
|
+
"line-height",
|
|
2723
|
+
"letter-spacing",
|
|
2724
|
+
"other"
|
|
2725
|
+
];
|
|
2726
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2727
|
+
const fallback = [];
|
|
2728
|
+
for (const token of tokens) {
|
|
2729
|
+
const key = this.getFontCollectionKey(token.name);
|
|
2730
|
+
if (!key) {
|
|
2731
|
+
fallback.push(token);
|
|
2732
|
+
continue;
|
|
2733
|
+
}
|
|
2734
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
2735
|
+
grouped.get(key).push(token);
|
|
2736
|
+
}
|
|
2737
|
+
if (fallback.length > 0) grouped.set("other", fallback);
|
|
2738
|
+
const result = [];
|
|
2739
|
+
for (const key of order) {
|
|
2740
|
+
const entries = grouped.get(key);
|
|
2741
|
+
if (!entries || entries.length === 0) continue;
|
|
2742
|
+
result.push({
|
|
2743
|
+
key,
|
|
2744
|
+
title: titles.get(key) ?? key,
|
|
2745
|
+
entries: [...entries].sort((left, right) => left.name.localeCompare(right.name))
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
return result;
|
|
2749
|
+
}
|
|
2750
|
+
getFontCollectionKey(name) {
|
|
2751
|
+
const normalized = name.replace(/^--/, "").toLowerCase();
|
|
2752
|
+
for (const pattern of [
|
|
2753
|
+
"font-family",
|
|
2754
|
+
"font-weight",
|
|
2755
|
+
"font-size",
|
|
2756
|
+
"line-height",
|
|
2757
|
+
"letter-spacing"
|
|
2758
|
+
]) if (normalized.includes(pattern)) return pattern;
|
|
2759
|
+
return null;
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
/**
|
|
2763
|
+
* Groups border tokens into models for the HTML showcase.
|
|
2764
|
+
*
|
|
2765
|
+
* @remarks
|
|
2766
|
+
* Combines full border tokens and their parts: color, width and style.
|
|
2767
|
+
* Separates border-width and stroke-style tokens, and returns
|
|
2768
|
+
* unsupported values as fallback.
|
|
2769
|
+
*/
|
|
2770
|
+
var BorderTokenAggregator = class {
|
|
2771
|
+
aggregate(tokens) {
|
|
2772
|
+
const aggregated = this.aggregateBorderTokens(tokens);
|
|
2773
|
+
const strokeStyles = this.aggregateStrokeStyleTokens(aggregated.fallback);
|
|
2774
|
+
const strokeStyleNames = new Set(strokeStyles.flatMap((item) => this.getStrokeStyleTokenNames(item)));
|
|
2775
|
+
const borderWidths = aggregated.fallback.map((token) => this.toBorderWidthTokenInfo(token)).filter((token) => token !== null);
|
|
2776
|
+
const borderWidthNames = new Set(borderWidths.map((token) => this.normalizeTokenName(token.name)));
|
|
2777
|
+
const fallback = aggregated.fallback.filter((token) => !strokeStyleNames.has(token.name) && !borderWidthNames.has(this.normalizeTokenName(token.name)));
|
|
2778
|
+
return {
|
|
2779
|
+
borders: aggregated.items,
|
|
2780
|
+
borderWidths,
|
|
2781
|
+
strokeStyles,
|
|
2782
|
+
fallback
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
aggregateBorderTokens(tokens) {
|
|
2786
|
+
const map = /* @__PURE__ */ new Map();
|
|
2787
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
2788
|
+
for (const token of tokens) {
|
|
2789
|
+
const parsed = this.parseBorderTokenName(token.name);
|
|
2790
|
+
if (!parsed) continue;
|
|
2791
|
+
const item = map.get(parsed.baseName) ?? { name: parsed.baseName };
|
|
2792
|
+
if (parsed.property === "value") item.value = token.value;
|
|
2793
|
+
else item[parsed.property] = token.value;
|
|
2794
|
+
map.set(parsed.baseName, item);
|
|
2795
|
+
consumed.add(token.name);
|
|
2796
|
+
}
|
|
2797
|
+
const items = [...map.values()].filter((item) => item.value && (item.color || item.width || item.style)).sort((left, right) => left.name.localeCompare(right.name));
|
|
2798
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
2799
|
+
for (const item of items) {
|
|
2800
|
+
usedNames.add(`--${item.name}`);
|
|
2801
|
+
usedNames.add(`--${item.name}-color`);
|
|
2802
|
+
usedNames.add(`--${item.name}-width`);
|
|
2803
|
+
usedNames.add(`--${item.name}-style`);
|
|
2804
|
+
}
|
|
2805
|
+
return {
|
|
2806
|
+
items,
|
|
2807
|
+
fallback: tokens.filter((token) => !consumed.has(token.name) || !usedNames.has(token.name))
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
parseBorderTokenName(name) {
|
|
2811
|
+
const normalized = name.replace(/^--/, "");
|
|
2812
|
+
const propertyMatch = normalized.match(/^(.*-border-.+)-(color|width|style)$/);
|
|
2813
|
+
if (propertyMatch) return {
|
|
2814
|
+
baseName: propertyMatch[1],
|
|
2815
|
+
property: propertyMatch[2]
|
|
2816
|
+
};
|
|
2817
|
+
if (/.*-border-.+/.test(normalized) && !normalized.includes("-border-width-")) return {
|
|
2818
|
+
baseName: normalized,
|
|
2819
|
+
property: "value"
|
|
2820
|
+
};
|
|
2821
|
+
return null;
|
|
2822
|
+
}
|
|
2823
|
+
toBorderWidthTokenInfo(token) {
|
|
2824
|
+
const normalized = token.name.replace(/^--/, "");
|
|
2825
|
+
if (!/-border-width-/.test(normalized)) return null;
|
|
2826
|
+
return {
|
|
2827
|
+
name: normalized,
|
|
2828
|
+
value: token.value
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
aggregateStrokeStyleTokens(tokens) {
|
|
2832
|
+
const map = /* @__PURE__ */ new Map();
|
|
2833
|
+
for (const token of tokens) {
|
|
2834
|
+
const parsed = this.parseStrokeStyleTokenName(token.name);
|
|
2835
|
+
if (!parsed) continue;
|
|
2836
|
+
const item = map.get(parsed.baseName) ?? {
|
|
2837
|
+
name: parsed.baseName,
|
|
2838
|
+
dashArray: []
|
|
2839
|
+
};
|
|
2840
|
+
if (parsed.property === "value") item.value = token.value;
|
|
2841
|
+
else if (parsed.property === "lineCap") item.lineCap = token.value;
|
|
2842
|
+
else item.dashArray[parsed.index ?? item.dashArray.length] = token.value;
|
|
2843
|
+
map.set(parsed.baseName, item);
|
|
2844
|
+
}
|
|
2845
|
+
return [...map.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
2846
|
+
}
|
|
2847
|
+
parseStrokeStyleTokenName(name) {
|
|
2848
|
+
const normalized = name.replace(/^--/, "");
|
|
2849
|
+
const dashArrayMatch = normalized.match(/^(.*-stroke-style-.+)-dashArray-(\d+)$/);
|
|
2850
|
+
if (dashArrayMatch) return {
|
|
2851
|
+
baseName: dashArrayMatch[1],
|
|
2852
|
+
property: "dashArray",
|
|
2853
|
+
index: Number(dashArrayMatch[2])
|
|
2854
|
+
};
|
|
2855
|
+
const lineCapMatch = normalized.match(/^(.*-stroke-style-.+)-lineCap$/);
|
|
2856
|
+
if (lineCapMatch) return {
|
|
2857
|
+
baseName: lineCapMatch[1],
|
|
2858
|
+
property: "lineCap"
|
|
2859
|
+
};
|
|
2860
|
+
if (/.*-stroke-style-.+/.test(normalized)) return {
|
|
2861
|
+
baseName: normalized,
|
|
2862
|
+
property: "value"
|
|
2863
|
+
};
|
|
2864
|
+
return null;
|
|
2865
|
+
}
|
|
2866
|
+
getStrokeStyleTokenNames(item) {
|
|
2867
|
+
const names = [`--${item.name}`];
|
|
2868
|
+
if (item.lineCap !== void 0) names.push(`--${item.name}-lineCap`);
|
|
2869
|
+
item.dashArray.forEach((_value, index) => {
|
|
2870
|
+
names.push(`--${item.name}-dashArray-${index}`);
|
|
2871
|
+
});
|
|
2872
|
+
return names;
|
|
2873
|
+
}
|
|
2874
|
+
normalizeTokenName(name) {
|
|
2875
|
+
return name.replace(/^--/, "");
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
/**
|
|
2879
|
+
* Groups shadow tokens into models for the HTML showcase.
|
|
2880
|
+
*
|
|
2881
|
+
* @remarks
|
|
2882
|
+
* Combines single-layer and multi-layer shadows from separate CSS variables:
|
|
2883
|
+
* offsetX, offsetY, blur, spread and color.
|
|
2884
|
+
*
|
|
2885
|
+
* Returns unsupported or incomplete values as fallback.
|
|
2886
|
+
*/
|
|
2887
|
+
var ShadowTokenAggregator = class {
|
|
2888
|
+
aggregate(tokens) {
|
|
2889
|
+
const map = /* @__PURE__ */ new Map();
|
|
2890
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
2891
|
+
for (const token of tokens) {
|
|
2892
|
+
const parsed = this.parseShadowTokenName(token.name);
|
|
2893
|
+
if (!parsed) continue;
|
|
2894
|
+
const item = map.get(parsed.baseName) ?? {
|
|
2895
|
+
name: parsed.baseName,
|
|
2896
|
+
layers: []
|
|
2897
|
+
};
|
|
2898
|
+
if (parsed.property === "value") item.value = token.value;
|
|
2899
|
+
else {
|
|
2900
|
+
const layer = item.layers[parsed.layerIndex] ?? {};
|
|
2901
|
+
layer[parsed.property] = token.value;
|
|
2902
|
+
item.layers[parsed.layerIndex] = layer;
|
|
2903
|
+
}
|
|
2904
|
+
map.set(parsed.baseName, item);
|
|
2905
|
+
consumed.add(token.name);
|
|
2906
|
+
}
|
|
2907
|
+
const items = [...map.values()].map((item) => ({
|
|
2908
|
+
...item,
|
|
2909
|
+
layers: item.layers.filter((layer) => layer !== void 0)
|
|
2910
|
+
})).filter((item) => item.value && item.layers.length > 0).sort((left, right) => left.name.localeCompare(right.name));
|
|
2911
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
2912
|
+
for (const item of items) {
|
|
2913
|
+
usedNames.add(`--${item.name}`);
|
|
2914
|
+
item.layers.forEach((_layer, index) => {
|
|
2915
|
+
const layerPrefix = item.layers.length > 1 ? `${item.name}-${index}` : item.name;
|
|
2916
|
+
usedNames.add(`--${layerPrefix}-offsetX`);
|
|
2917
|
+
usedNames.add(`--${layerPrefix}-offsetY`);
|
|
2918
|
+
usedNames.add(`--${layerPrefix}-blur`);
|
|
2919
|
+
usedNames.add(`--${layerPrefix}-spread`);
|
|
2920
|
+
usedNames.add(`--${layerPrefix}-color`);
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
return {
|
|
2924
|
+
items,
|
|
2925
|
+
fallback: tokens.filter((token) => !consumed.has(token.name) || !usedNames.has(token.name))
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
parseShadowTokenName(name) {
|
|
2929
|
+
const normalized = name.replace(/^--/, "");
|
|
2930
|
+
const layerMatch = normalized.match(/^(.*-shadow-.+)-(\d+)-(offsetX|offsetY|blur|spread|color)$/);
|
|
2931
|
+
if (layerMatch) return {
|
|
2932
|
+
baseName: layerMatch[1],
|
|
2933
|
+
property: layerMatch[3],
|
|
2934
|
+
layerIndex: Number(layerMatch[2])
|
|
2935
|
+
};
|
|
2936
|
+
const propertyMatch = normalized.match(/^(.*-shadow-.+)-(offsetX|offsetY|blur|spread|color)$/);
|
|
2937
|
+
if (propertyMatch) return {
|
|
2938
|
+
baseName: propertyMatch[1],
|
|
2939
|
+
property: propertyMatch[2],
|
|
2940
|
+
layerIndex: 0
|
|
2941
|
+
};
|
|
2942
|
+
if (/.*-shadow-.+/.test(normalized)) return {
|
|
2943
|
+
baseName: normalized,
|
|
2944
|
+
property: "value",
|
|
2945
|
+
layerIndex: 0
|
|
2946
|
+
};
|
|
2947
|
+
return null;
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
/**
|
|
2951
|
+
* Groups gradient tokens into models for the HTML showcase.
|
|
2952
|
+
*
|
|
2953
|
+
* @remarks
|
|
2954
|
+
* Combines gradient stop parts: color and position.
|
|
2955
|
+
* The result is used by the renderer to build preview via CSS gradient.
|
|
2956
|
+
*
|
|
2957
|
+
* Returns unsupported or incomplete values as fallback.
|
|
2958
|
+
*/
|
|
2959
|
+
var GradientTokenAggregator = class {
|
|
2960
|
+
aggregate(tokens) {
|
|
2961
|
+
const map = /* @__PURE__ */ new Map();
|
|
2962
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
2963
|
+
for (const token of tokens) {
|
|
2964
|
+
const parsed = this.parseGradientTokenName(token.name);
|
|
2965
|
+
if (!parsed) continue;
|
|
2966
|
+
const item = map.get(parsed.baseName) ?? {
|
|
2967
|
+
name: parsed.baseName,
|
|
2968
|
+
stops: []
|
|
2969
|
+
};
|
|
2970
|
+
if (parsed.property === "value") item.value = token.value;
|
|
2971
|
+
else {
|
|
2972
|
+
const stop = item.stops[parsed.stopIndex] ?? {};
|
|
2973
|
+
stop[parsed.property] = token.value;
|
|
2974
|
+
item.stops[parsed.stopIndex] = stop;
|
|
2975
|
+
}
|
|
2976
|
+
map.set(parsed.baseName, item);
|
|
2977
|
+
consumed.add(token.name);
|
|
2978
|
+
}
|
|
2979
|
+
const items = [...map.values()].map((item) => ({
|
|
2980
|
+
...item,
|
|
2981
|
+
stops: item.stops.filter((stop) => stop?.color)
|
|
2982
|
+
})).filter((item) => item.stops.length > 0).sort((left, right) => left.name.localeCompare(right.name));
|
|
2983
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
2984
|
+
for (const item of items) item.stops.forEach((_stop, index) => {
|
|
2985
|
+
usedNames.add(`--${item.name}-${index}-color`);
|
|
2986
|
+
usedNames.add(`--${item.name}-${index}-position`);
|
|
2987
|
+
});
|
|
2988
|
+
return {
|
|
2989
|
+
items,
|
|
2990
|
+
fallback: tokens.filter((token) => !consumed.has(token.name) || !usedNames.has(token.name))
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
parseGradientTokenName(name) {
|
|
2994
|
+
const normalized = name.replace(/^--/, "");
|
|
2995
|
+
const stopMatch = normalized.match(/^(.*-gradient-.+)-(\d+)-(color|position)$/);
|
|
2996
|
+
if (stopMatch) return {
|
|
2997
|
+
baseName: stopMatch[1],
|
|
2998
|
+
property: stopMatch[3],
|
|
2999
|
+
stopIndex: Number(stopMatch[2])
|
|
3000
|
+
};
|
|
3001
|
+
if (/.*-gradient-.+/.test(normalized)) return {
|
|
3002
|
+
baseName: normalized,
|
|
3003
|
+
property: "value",
|
|
3004
|
+
stopIndex: 0
|
|
3005
|
+
};
|
|
3006
|
+
return null;
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
/**
|
|
3010
|
+
* Groups transition tokens into models for the HTML showcase.
|
|
3011
|
+
*
|
|
3012
|
+
* @remarks
|
|
3013
|
+
* Combines duration, delay and timingFunction parts of one transition.
|
|
3014
|
+
* The result is used by the renderer to show motion cards.
|
|
3015
|
+
*
|
|
3016
|
+
* Returns unsupported values as fallback.
|
|
3017
|
+
*/
|
|
3018
|
+
var TransitionTokenAggregator = class {
|
|
3019
|
+
aggregate(tokens) {
|
|
3020
|
+
const map = /* @__PURE__ */ new Map();
|
|
3021
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
3022
|
+
for (const token of tokens) {
|
|
3023
|
+
const parsed = this.parseTransitionTokenName(token.name);
|
|
3024
|
+
if (!parsed) continue;
|
|
3025
|
+
const item = map.get(parsed.baseName) ?? {
|
|
3026
|
+
name: parsed.baseName,
|
|
3027
|
+
timingFunction: []
|
|
3028
|
+
};
|
|
3029
|
+
if (parsed.property === "value") item.value = token.value;
|
|
3030
|
+
else if (parsed.property === "timingFunction") item.timingFunction[parsed.index ?? item.timingFunction.length] = token.value;
|
|
3031
|
+
else item[parsed.property] = token.value;
|
|
3032
|
+
map.set(parsed.baseName, item);
|
|
3033
|
+
consumed.add(token.name);
|
|
3034
|
+
}
|
|
3035
|
+
const items = [...map.values()].filter((item) => item.value || item.duration || item.timingFunction.length > 0 || item.delay).sort((left, right) => left.name.localeCompare(right.name));
|
|
3036
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
3037
|
+
for (const item of items) {
|
|
3038
|
+
usedNames.add(`--${item.name}`);
|
|
3039
|
+
usedNames.add(`--${item.name}-duration`);
|
|
3040
|
+
usedNames.add(`--${item.name}-delay`);
|
|
3041
|
+
item.timingFunction.forEach((_value, index) => {
|
|
3042
|
+
usedNames.add(`--${item.name}-timingFunction-${index}`);
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
return {
|
|
3046
|
+
items,
|
|
3047
|
+
fallback: tokens.filter((token) => !consumed.has(token.name) || !usedNames.has(token.name))
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
parseTransitionTokenName(name) {
|
|
3051
|
+
const normalized = name.replace(/^--/, "");
|
|
3052
|
+
const timingMatch = normalized.match(/^(.*-transition-.+)-timingFunction-(\d+)$/);
|
|
3053
|
+
if (timingMatch) return {
|
|
3054
|
+
baseName: timingMatch[1],
|
|
3055
|
+
property: "timingFunction",
|
|
3056
|
+
index: Number(timingMatch[2])
|
|
3057
|
+
};
|
|
3058
|
+
const propertyMatch = normalized.match(/^(.*-transition-.+)-(duration|delay)$/);
|
|
3059
|
+
if (propertyMatch) return {
|
|
3060
|
+
baseName: propertyMatch[1],
|
|
3061
|
+
property: propertyMatch[2]
|
|
3062
|
+
};
|
|
3063
|
+
if (/.*-transition-.+/.test(normalized)) return {
|
|
3064
|
+
baseName: normalized,
|
|
3065
|
+
property: "value"
|
|
3066
|
+
};
|
|
3067
|
+
return null;
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3070
|
+
//#endregion
|
|
3071
|
+
//#region src/core/showcase/TokenGroupClassifier.ts
|
|
3072
|
+
/**
|
|
3073
|
+
* Groups tokens for display in the HTML showcase.
|
|
3074
|
+
*
|
|
3075
|
+
* @remarks
|
|
3076
|
+
* Sorts tokens by scope: `primitive`, `semantic`, `component`,
|
|
3077
|
+
* separates primitive themes and sorts groups in a stable order.
|
|
3078
|
+
*
|
|
3079
|
+
* For visual sections uses simple heuristics by name and value
|
|
3080
|
+
* of CSS variable: colors, typography, spacing, shadows, motion and others.
|
|
3081
|
+
*/
|
|
3082
|
+
var TokenGroupClassifier = class TokenGroupClassifier {
|
|
3083
|
+
static #COLOR_HEX_RE = /^#[0-9a-fA-F]{3,8}$/;
|
|
3084
|
+
groupEntriesByScope(entries) {
|
|
3085
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
3086
|
+
for (const entry of entries) {
|
|
3087
|
+
if (!scopes.has(entry.scope)) scopes.set(entry.scope, []);
|
|
3088
|
+
scopes.get(entry.scope).push({
|
|
3089
|
+
name: entry.name,
|
|
3090
|
+
value: entry.value
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
return scopes;
|
|
3094
|
+
}
|
|
3095
|
+
getPrimitiveThemes(themes) {
|
|
3096
|
+
return themes.map((theme) => ({
|
|
3097
|
+
name: theme.name,
|
|
3098
|
+
entries: theme.entries.filter((entry) => entry.scope === "primitive")
|
|
3099
|
+
})).filter((theme) => theme.entries.length > 0);
|
|
3100
|
+
}
|
|
3101
|
+
getOrderedScopes(scopes) {
|
|
3102
|
+
const priority = new Map([
|
|
3103
|
+
["primitive", 0],
|
|
3104
|
+
["semantic", 1],
|
|
3105
|
+
["component", 2]
|
|
3106
|
+
]);
|
|
3107
|
+
return [...scopes.entries()].sort(([left], [right]) => {
|
|
3108
|
+
const leftPriority = priority.get(left) ?? 99;
|
|
3109
|
+
const rightPriority = priority.get(right) ?? 99;
|
|
3110
|
+
if (leftPriority !== rightPriority) return leftPriority - rightPriority;
|
|
3111
|
+
return left.localeCompare(right);
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
groupTokens(tokens) {
|
|
3115
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3116
|
+
const order = [
|
|
3117
|
+
"colors",
|
|
3118
|
+
"fonts",
|
|
3119
|
+
"typography",
|
|
3120
|
+
"borders",
|
|
3121
|
+
"radius",
|
|
3122
|
+
"spacing",
|
|
3123
|
+
"shadows",
|
|
3124
|
+
"gradients",
|
|
3125
|
+
"motion",
|
|
3126
|
+
"opacity",
|
|
3127
|
+
"sizes",
|
|
3128
|
+
"other"
|
|
3129
|
+
];
|
|
3130
|
+
for (const group of order) grouped.set(group, []);
|
|
3131
|
+
for (const token of tokens) {
|
|
3132
|
+
const group = this.classifyTokenGroup(token.name, token.value);
|
|
3133
|
+
grouped.get(group).push(token);
|
|
3134
|
+
}
|
|
3135
|
+
const result = /* @__PURE__ */ new Map();
|
|
3136
|
+
for (const group of order) {
|
|
3137
|
+
const groupTokens = grouped.get(group);
|
|
3138
|
+
if (groupTokens.length > 0) result.set(group, groupTokens);
|
|
3139
|
+
}
|
|
3140
|
+
return result;
|
|
3141
|
+
}
|
|
3142
|
+
classifyTokenGroup(name, value) {
|
|
3143
|
+
const lowerName = name.toLowerCase();
|
|
3144
|
+
const lowerValue = value.toLowerCase();
|
|
3145
|
+
if (lowerName.includes("typography") || lowerValue.includes("typography") || lowerName.includes("-text-") || lowerName.includes("-body-") || lowerName.includes("-title-") || lowerName.includes("-code-")) return "typography";
|
|
3146
|
+
if (lowerName.includes("font-family") || lowerName.includes("font-size") || lowerName.includes("font-weight") || lowerName.includes("line-height") || lowerName.includes("lineheight") || lowerName.includes("letter-spacing") || lowerName.includes("tracking")) return "fonts";
|
|
3147
|
+
if (lowerName.includes("border") || lowerName.includes("stroke") || lowerName.includes("outline")) return "borders";
|
|
3148
|
+
if (lowerName.includes("shadow") || lowerName.includes("elevation")) return "shadows";
|
|
3149
|
+
if (lowerName.includes("gradient")) return "gradients";
|
|
3150
|
+
if (lowerName.includes("color") || TokenGroupClassifier.#COLOR_HEX_RE.test(value) || lowerValue.startsWith("rgb") || lowerValue.startsWith("hsl")) return "colors";
|
|
3151
|
+
if (lowerName.includes("radius") || lowerName.includes("rounded")) return "radius";
|
|
3152
|
+
if (lowerName.includes("space") || lowerName.includes("spacing") || lowerName.includes("gap") || lowerName.includes("padding") || lowerName.includes("margin") || lowerName.includes("inset")) return "spacing";
|
|
3153
|
+
if (lowerName.includes("duration") || lowerName.includes("easing") || lowerName.includes("cubic-bezier") || lowerName.includes("transition") || lowerName.includes("animation") || lowerValue.includes("cubic-bezier") || lowerValue.includes("ms") || lowerValue.endsWith("s")) return "motion";
|
|
3154
|
+
if (lowerName.includes("opacity") || lowerName.includes("alpha")) return "opacity";
|
|
3155
|
+
if (lowerName.includes("size") || lowerName.includes("width") || lowerName.includes("height")) return "sizes";
|
|
3156
|
+
return "other";
|
|
3157
|
+
}
|
|
3158
|
+
};
|
|
3159
|
+
//#endregion
|
|
3160
|
+
//#region src/core/showcase/TokenHtmlShowcaseRenderer.ts
|
|
3161
|
+
/**
|
|
3162
|
+
* Renders an HTML page for the token showcase.
|
|
3163
|
+
*
|
|
3164
|
+
* @remarks
|
|
3165
|
+
* Gets prepared token data and builds a complete HTML page:
|
|
3166
|
+
* layout, sidebar, sections, cards, preview elements and navigation script.
|
|
3167
|
+
*/
|
|
3168
|
+
var TokenHtmlShowcaseRenderer = class TokenHtmlShowcaseRenderer {
|
|
3169
|
+
static #COLOR_HEX_RE = /^#[0-9a-fA-F]{3,8}$/;
|
|
3170
|
+
static #FONT_PREVIEW_TEXT = "On sector 42-B, a lone Jedi followed 3 fading signals through the desert night.";
|
|
3171
|
+
static #SEMANTIC_GROUP_ORDER = [
|
|
3172
|
+
"color.text",
|
|
3173
|
+
"color.background",
|
|
3174
|
+
"color.border",
|
|
3175
|
+
"color.action",
|
|
3176
|
+
"color.status",
|
|
3177
|
+
"space.inset",
|
|
3178
|
+
"space.inline",
|
|
3179
|
+
"space.stack",
|
|
3180
|
+
"shape.radius",
|
|
3181
|
+
"shape.border",
|
|
3182
|
+
"text",
|
|
3183
|
+
"motion",
|
|
3184
|
+
"other"
|
|
3185
|
+
];
|
|
3186
|
+
static #SEMANTIC_GROUP_TITLES = new Map([
|
|
3187
|
+
["color.text", "Text colors"],
|
|
3188
|
+
["color.background", "Background colors"],
|
|
3189
|
+
["color.border", "Border colors"],
|
|
3190
|
+
["color.action", "Action colors"],
|
|
3191
|
+
["color.status", "Status colors"],
|
|
3192
|
+
["space.inset", "Inset spacing"],
|
|
3193
|
+
["space.inline", "Inline spacing"],
|
|
3194
|
+
["space.stack", "Stack spacing"],
|
|
3195
|
+
["shape.radius", "Radius roles"],
|
|
3196
|
+
["shape.border", "Border roles"],
|
|
3197
|
+
["text", "Text styles"],
|
|
3198
|
+
["motion", "Motion roles"],
|
|
3199
|
+
["other", "Other semantic roles"]
|
|
3200
|
+
]);
|
|
3201
|
+
#classifier;
|
|
3202
|
+
#typographyAggregator = new TypographyTokenAggregator();
|
|
3203
|
+
#fontAggregator = new FontCollectionAggregator();
|
|
3204
|
+
#borderAggregator = new BorderTokenAggregator();
|
|
3205
|
+
#shadowAggregator = new ShadowTokenAggregator();
|
|
3206
|
+
#gradientAggregator = new GradientTokenAggregator();
|
|
3207
|
+
#transitionAggregator = new TransitionTokenAggregator();
|
|
3208
|
+
constructor(classifier = new TokenGroupClassifier()) {
|
|
3209
|
+
this.#classifier = classifier;
|
|
3210
|
+
}
|
|
3211
|
+
renderPage(parsed) {
|
|
3212
|
+
const scopes = this.#classifier.groupEntriesByScope(parsed.entries);
|
|
3213
|
+
const visibleScopes = this.getVisibleScopes(scopes);
|
|
3214
|
+
const visibleThemes = this.getVisibleThemes(parsed.themes);
|
|
3215
|
+
if (visibleScopes.size === 0) return `<!DOCTYPE html>
|
|
3216
|
+
<html lang="en">
|
|
3217
|
+
<head>
|
|
3218
|
+
<meta charset="UTF-8">
|
|
3219
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3220
|
+
<title>Design Tokens - showcase</title>
|
|
3221
|
+
<style>
|
|
3222
|
+
${TOKEN_HTML_SHOWCASE_CSS}
|
|
3223
|
+
body{padding:2rem}
|
|
3224
|
+
</style>
|
|
3225
|
+
</head>
|
|
3226
|
+
<body>
|
|
3227
|
+
<h1>Design Tokens - showcase</h1>
|
|
3228
|
+
<p style="color:#64748b">No tokens found in the CSS output.</p>
|
|
3229
|
+
</body>
|
|
3230
|
+
</html>`;
|
|
3231
|
+
return `<!DOCTYPE html>
|
|
3232
|
+
<html lang="en">
|
|
3233
|
+
<head>
|
|
3234
|
+
<meta charset="UTF-8">
|
|
3235
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3236
|
+
<title>Design Tokens - showcase</title>
|
|
3237
|
+
<style>
|
|
3238
|
+
${TOKEN_HTML_SHOWCASE_CSS}
|
|
3239
|
+
</style>
|
|
3240
|
+
</head>
|
|
3241
|
+
<body>
|
|
3242
|
+
<header class="page-header"><h1 class="page-title">Design Tokens - showcase</h1></header>
|
|
3243
|
+
<div class="page-shell">
|
|
3244
|
+
<aside class="sidebar">
|
|
3245
|
+
<div class="sidebar-title">Token Groups</div>
|
|
3246
|
+
<nav class="sidebar-nav">
|
|
3247
|
+
${this.renderMenu(visibleScopes, visibleThemes)}
|
|
3248
|
+
</nav>
|
|
3249
|
+
</aside>
|
|
3250
|
+
<main class="content">
|
|
3251
|
+
${this.renderThemeSummary(visibleThemes)}
|
|
3252
|
+
${this.renderTokens(visibleScopes, visibleThemes, parsed.entries)}
|
|
3253
|
+
</main>
|
|
3254
|
+
</div>
|
|
3255
|
+
<button class="back-to-top" type="button" aria-label="Back to top" title="Back to top">
|
|
3256
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m18 15-6-6-6 6"></path></svg>
|
|
3257
|
+
</button>
|
|
3258
|
+
<script>
|
|
3259
|
+
(() => {
|
|
3260
|
+
const OFFSET = 112;
|
|
3261
|
+
function scrollToAnchor(id, smooth) {
|
|
3262
|
+
if (!id) return;
|
|
3263
|
+
const el = document.getElementById(id);
|
|
3264
|
+
if (!el) return;
|
|
3265
|
+
openMenuForAnchor(id);
|
|
3266
|
+
const top = el.getBoundingClientRect().top + window.pageYOffset - OFFSET;
|
|
3267
|
+
window.scrollTo({ top, behavior: smooth ? "smooth" : "auto" });
|
|
3268
|
+
}
|
|
3269
|
+
function setMenuPanel(key, expanded) {
|
|
3270
|
+
if (!key) return;
|
|
3271
|
+
const toggle = document.querySelector('[data-menu-toggle="' + key + '"]');
|
|
3272
|
+
if (expanded && toggle && toggle.dataset.menuLevel === "root") {
|
|
3273
|
+
document.querySelectorAll('[data-menu-toggle][data-menu-level="root"]').forEach((button) => {
|
|
3274
|
+
if (button.dataset.menuToggle === key) return;
|
|
3275
|
+
button.setAttribute("aria-expanded", "false");
|
|
3276
|
+
document.querySelectorAll('[data-menu-panel="' + button.dataset.menuToggle + '"]').forEach((panel) => {
|
|
3277
|
+
panel.hidden = true;
|
|
3278
|
+
});
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
document.querySelectorAll("[data-menu-panel]").forEach((panel) => {
|
|
3282
|
+
if (panel.dataset.menuPanel === key) {
|
|
3283
|
+
panel.hidden = !expanded;
|
|
3284
|
+
}
|
|
3285
|
+
});
|
|
3286
|
+
document.querySelectorAll("[data-menu-toggle]").forEach((button) => {
|
|
3287
|
+
if (button.dataset.menuToggle === key) {
|
|
3288
|
+
button.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
3289
|
+
}
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
function openMenuForAnchor(id) {
|
|
3293
|
+
let matched = false;
|
|
3294
|
+
document.querySelectorAll('a.sidebar-sub-link[href^="#"]').forEach((link) => {
|
|
3295
|
+
if (link.getAttribute("href") !== "#" + id) return;
|
|
3296
|
+
matched = true;
|
|
3297
|
+
let panel = link.closest("[data-menu-panel]");
|
|
3298
|
+
while (panel) {
|
|
3299
|
+
setMenuPanel(panel.dataset.menuPanel, true);
|
|
3300
|
+
panel = panel.parentElement ? panel.parentElement.closest("[data-menu-panel]") : null;
|
|
3301
|
+
}
|
|
3302
|
+
});
|
|
3303
|
+
if (!matched && id.startsWith("scope-")) {
|
|
3304
|
+
const scope = id.slice("scope-".length);
|
|
3305
|
+
const panel = document.querySelector('[data-menu-scope="' + scope + '"]');
|
|
3306
|
+
if (panel) {
|
|
3307
|
+
setMenuPanel(panel.dataset.menuPanel, true);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
function openInitialMenu() {
|
|
3312
|
+
const initial = document.querySelector("[data-menu-default]");
|
|
3313
|
+
if (initial) {
|
|
3314
|
+
setMenuPanel(initial.dataset.menuToggle, true);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
document.querySelectorAll("[data-menu-toggle]").forEach((button) => {
|
|
3318
|
+
button.addEventListener("click", () => {
|
|
3319
|
+
const key = button.dataset.menuToggle;
|
|
3320
|
+
const expanded = button.getAttribute("aria-expanded") === "true";
|
|
3321
|
+
setMenuPanel(key, !expanded);
|
|
3322
|
+
});
|
|
3323
|
+
});
|
|
3324
|
+
document.querySelectorAll("a.sidebar-sub-link").forEach((link) => {
|
|
3325
|
+
link.addEventListener("click", (event) => {
|
|
3326
|
+
const href = link.getAttribute("href");
|
|
3327
|
+
if (!href || !href.startsWith("#")) return;
|
|
3328
|
+
event.preventDefault();
|
|
3329
|
+
const id = href.slice(1);
|
|
3330
|
+
history.replaceState(null, "", href);
|
|
3331
|
+
scrollToAnchor(id, true);
|
|
3332
|
+
});
|
|
3333
|
+
});
|
|
3334
|
+
document.querySelector(".back-to-top")?.addEventListener("click", () => {
|
|
3335
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
3336
|
+
});
|
|
3337
|
+
window.addEventListener("hashchange", () => {
|
|
3338
|
+
scrollToAnchor(window.location.hash.slice(1), false);
|
|
3339
|
+
});
|
|
3340
|
+
if (window.location.hash) {
|
|
3341
|
+
setTimeout(() => scrollToAnchor(window.location.hash.slice(1), false), 0);
|
|
3342
|
+
} else {
|
|
3343
|
+
openInitialMenu();
|
|
3344
|
+
}
|
|
3345
|
+
})();
|
|
3346
|
+
<\/script>
|
|
3347
|
+
</body>
|
|
3348
|
+
</html>`;
|
|
3349
|
+
}
|
|
3350
|
+
getVisibleScopes(scopes) {
|
|
3351
|
+
return new Map([...scopes.entries()].filter(([scope]) => scope !== "component"));
|
|
3352
|
+
}
|
|
3353
|
+
getVisibleThemes(themes) {
|
|
3354
|
+
return themes.map((theme) => ({
|
|
3355
|
+
name: theme.name,
|
|
3356
|
+
entries: theme.entries.filter((entry) => entry.scope !== "component")
|
|
3357
|
+
})).filter((theme) => theme.entries.length > 0);
|
|
3358
|
+
}
|
|
3359
|
+
renderMenu(scopes, themes) {
|
|
3360
|
+
if (themes.length > 0) return this.renderThemedMenu(themes);
|
|
3361
|
+
return this.renderScopeMenu(scopes);
|
|
3362
|
+
}
|
|
3363
|
+
renderThemedMenu(themes) {
|
|
3364
|
+
let html = "";
|
|
3365
|
+
for (const [index, theme] of themes.entries()) {
|
|
3366
|
+
const themeKey = this.sectionId("menu-theme", theme.name);
|
|
3367
|
+
html += `<section class="sidebar-group sidebar-group--theme">\n`;
|
|
3368
|
+
html += this.renderSidebarToggle(theme.name, themeKey, "sidebar-toggle--theme", "root", index === 0);
|
|
3369
|
+
html += `<div class="sidebar-group-body sidebar-group-body--theme" data-menu-panel="${this.esc(themeKey)}" hidden>\n`;
|
|
3370
|
+
const themeScopes = this.getVisibleScopes(this.#classifier.groupEntriesByScope(theme.entries));
|
|
3371
|
+
for (const [scope, tokens] of this.#classifier.getOrderedScopes(themeScopes)) {
|
|
3372
|
+
const scopeKey = this.sectionId("menu-theme-scope", `${theme.name}-${scope}`);
|
|
3373
|
+
html += `<section class="sidebar-group sidebar-group--nested">\n`;
|
|
3374
|
+
html += this.renderSidebarToggle(scope, scopeKey, "sidebar-toggle--scope", "nested", false);
|
|
3375
|
+
html += `<div class="sidebar-group-body sidebar-group-body--scope" data-menu-panel="${this.esc(scopeKey)}" data-menu-scope="${this.esc(scope)}" hidden>\n`;
|
|
3376
|
+
if (scope === "semantic") html += this.renderSemanticMenuLinks(tokens, `semantic-${theme.name}`);
|
|
3377
|
+
else html += this.renderMenuGroupLinks(`${scope}-${theme.name}`, tokens);
|
|
3378
|
+
html += `</div>\n`;
|
|
3379
|
+
html += `</section>\n`;
|
|
3380
|
+
}
|
|
3381
|
+
html += `</div>\n`;
|
|
3382
|
+
html += `</section>\n`;
|
|
3383
|
+
}
|
|
3384
|
+
return html;
|
|
3385
|
+
}
|
|
3386
|
+
renderScopeMenu(scopes) {
|
|
3387
|
+
let html = "";
|
|
3388
|
+
for (const [index, [scope, tokens]] of this.#classifier.getOrderedScopes(scopes).entries()) {
|
|
3389
|
+
const scopeKey = this.sectionId("menu-scope", scope);
|
|
3390
|
+
html += `<section class="sidebar-group">\n`;
|
|
3391
|
+
html += this.renderSidebarToggle(scope, scopeKey, "sidebar-toggle--scope", "root", index === 0);
|
|
3392
|
+
html += `<div class="sidebar-group-body sidebar-group-body--scope" data-menu-panel="${this.esc(scopeKey)}" data-menu-scope="${this.esc(scope)}" hidden>\n`;
|
|
3393
|
+
if (scope === "semantic") html += this.renderSemanticMenuLinks(tokens);
|
|
3394
|
+
else html += this.renderMenuGroupLinks(scope, tokens);
|
|
3395
|
+
html += `</div>\n`;
|
|
3396
|
+
html += `</section>\n`;
|
|
3397
|
+
}
|
|
3398
|
+
return html;
|
|
3399
|
+
}
|
|
3400
|
+
renderSemanticMenuLinks(tokens, idPrefix = "semantic") {
|
|
3401
|
+
return this.groupSemanticTokens(tokens).map((group) => {
|
|
3402
|
+
const subId = this.sectionId(`${idPrefix}-group-title`, group.key);
|
|
3403
|
+
return `<a class="sidebar-sub-link" href="#${this.esc(subId)}">${this.esc(group.title)}</a>`;
|
|
3404
|
+
}).join("\n");
|
|
3405
|
+
}
|
|
3406
|
+
renderSidebarToggle(label, key, modifier, level, isDefault) {
|
|
3407
|
+
const defaultAttr = isDefault ? ` data-menu-default="true"` : "";
|
|
3408
|
+
return `<button class="sidebar-link sidebar-toggle ${this.esc(modifier)}" type="button" data-menu-toggle="${this.esc(key)}" data-menu-level="${this.esc(level)}" aria-expanded="false"${defaultAttr}><span class="sidebar-chevron" aria-hidden="true"></span>${this.renderFolderIcon()}<span class="sidebar-label">${this.esc(label)}</span></button>\n`;
|
|
3409
|
+
}
|
|
3410
|
+
renderFolderIcon() {
|
|
3411
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-icon lucide lucide-folder" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"></path></svg>`;
|
|
3412
|
+
}
|
|
3413
|
+
renderThemeSummary(themes) {
|
|
3414
|
+
if (themes.length === 0) return "";
|
|
3415
|
+
const baseTokens = new Map(themes[0]?.entries.map((entry) => [entry.name, entry.value]) ?? []);
|
|
3416
|
+
return `<section class="theme-summary"><div class="theme-summary-title">Theme statistics</div><div class="theme-summary-grid">${themes.map((theme, index) => {
|
|
3417
|
+
const overrides = index === 0 ? 0 : theme.entries.filter((entry) => baseTokens.has(entry.name) && baseTokens.get(entry.name) !== entry.value).length;
|
|
3418
|
+
return `<article class="theme-summary-card"><div class="theme-summary-header"><span class="theme-summary-label">Theme name:</span><span class="theme-summary-name">${this.esc(theme.name)}</span></div><dl class="theme-summary-stats"><div><dt>Tokens</dt><dd>${theme.entries.length}</dd></div><div><dt>Overrides</dt><dd>${overrides}</dd></div></dl></article>`;
|
|
3419
|
+
}).join("\n")}</div></section>\n`;
|
|
3420
|
+
}
|
|
3421
|
+
renderTokens(scopes, themes, entries) {
|
|
3422
|
+
if (themes.length > 0) return this.renderThemedTokens(themes, entries);
|
|
3423
|
+
let html = "";
|
|
3424
|
+
for (const [scope, tokens] of this.#classifier.getOrderedScopes(scopes)) {
|
|
3425
|
+
html += `<section class="token-section" id="${this.esc(this.sectionId("scope", scope))}">\n`;
|
|
3426
|
+
if (scope === "semantic") html += this.renderSemanticTokens(tokens, entries);
|
|
3427
|
+
else {
|
|
3428
|
+
html += `<h2 class="token-category">${this.esc(scope)}</h2>\n`;
|
|
3429
|
+
html += this.renderGroupedTokens(tokens, scope);
|
|
3430
|
+
}
|
|
3431
|
+
html += `</section>\n`;
|
|
3432
|
+
}
|
|
3433
|
+
return html;
|
|
3434
|
+
}
|
|
3435
|
+
renderThemedTokens(themes, entries) {
|
|
3436
|
+
let html = "";
|
|
3437
|
+
for (const theme of themes) {
|
|
3438
|
+
html += `<section class="theme-section" id="${this.esc(this.sectionId("primitive-theme", theme.name))}">\n`;
|
|
3439
|
+
html += `<h3 class="theme-title">Theme: ${this.esc(theme.name)}</h3>\n`;
|
|
3440
|
+
const themeScopes = this.getVisibleScopes(this.#classifier.groupEntriesByScope(theme.entries));
|
|
3441
|
+
for (const [scope, tokens] of this.#classifier.getOrderedScopes(themeScopes)) {
|
|
3442
|
+
html += `<section class="token-section token-section--theme-scope" id="${this.esc(this.sectionId("scope", `${theme.name}-${scope}`))}">\n`;
|
|
3443
|
+
html += `<h2 class="token-category">${this.esc(scope)}</h2>\n`;
|
|
3444
|
+
if (scope === "semantic") html += this.renderSemanticTokens(tokens, entries, `semantic-${theme.name}`);
|
|
3445
|
+
else html += this.renderGroupedTokens(tokens, `${scope}-${theme.name}`);
|
|
3446
|
+
html += `</section>\n`;
|
|
3447
|
+
}
|
|
3448
|
+
html += `</section>\n`;
|
|
3449
|
+
}
|
|
3450
|
+
return html;
|
|
3451
|
+
}
|
|
3452
|
+
renderSemanticTokens(tokens, entries, idPrefix = "semantic") {
|
|
3453
|
+
const tokenMap = this.createTokenMap(entries);
|
|
3454
|
+
const groups = this.groupSemanticTokens(tokens);
|
|
3455
|
+
if (groups.length === 0) return "";
|
|
3456
|
+
let html = `<section class="semantic-section">\n`;
|
|
3457
|
+
html += `<header class="section-header">\n`;
|
|
3458
|
+
html += `<h2>Semantic roles</h2>\n`;
|
|
3459
|
+
html += `<p>Semantic tokens describe how primitive values are used in the design system.</p>\n`;
|
|
3460
|
+
html += `</header>\n`;
|
|
3461
|
+
html += `<div class="semantic-groups">\n`;
|
|
3462
|
+
for (const group of groups) {
|
|
3463
|
+
html += `<section class="semantic-group">\n`;
|
|
3464
|
+
html += `<h3 id="${this.esc(this.sectionId(`${idPrefix}-group-title`, group.key))}">${this.esc(group.title)}</h3>\n`;
|
|
3465
|
+
html += group.tokens.map((token) => this.renderSemanticRole(group.key, token, tokenMap)).join("");
|
|
3466
|
+
html += `</section>\n`;
|
|
3467
|
+
}
|
|
3468
|
+
html += `</div>\n`;
|
|
3469
|
+
html += `</section>\n`;
|
|
3470
|
+
return html;
|
|
3471
|
+
}
|
|
3472
|
+
groupSemanticTokens(tokens) {
|
|
3473
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3474
|
+
for (const token of this.dedupeTokens(tokens)) {
|
|
3475
|
+
const parts = this.cssVariableToTokenPath(token.name).split(".");
|
|
3476
|
+
if (parts[0] !== "semantic") continue;
|
|
3477
|
+
const key = this.getSemanticGroupKey(parts);
|
|
3478
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
3479
|
+
grouped.get(key).push(token);
|
|
3480
|
+
}
|
|
3481
|
+
const result = [];
|
|
3482
|
+
for (const key of TokenHtmlShowcaseRenderer.#SEMANTIC_GROUP_ORDER) {
|
|
3483
|
+
const groupTokens = grouped.get(key);
|
|
3484
|
+
if (!groupTokens || groupTokens.length === 0) continue;
|
|
3485
|
+
result.push({
|
|
3486
|
+
key,
|
|
3487
|
+
title: TokenHtmlShowcaseRenderer.#SEMANTIC_GROUP_TITLES.get(key) ?? key,
|
|
3488
|
+
tokens: [...groupTokens].sort((left, right) => left.name.localeCompare(right.name))
|
|
3489
|
+
});
|
|
3490
|
+
}
|
|
3491
|
+
return result;
|
|
3492
|
+
}
|
|
3493
|
+
renderSemanticRole(groupKey, token, tokenMap) {
|
|
3494
|
+
const path = this.cssVariableToTokenPath(token.name);
|
|
3495
|
+
const roleName = this.getSemanticRoleName(path);
|
|
3496
|
+
const resolved = this.resolveTokenValue(token.value, tokenMap);
|
|
3497
|
+
const directRefs = this.extractVarReferences(token.value);
|
|
3498
|
+
const warningMessages = this.getSemanticWarnings(directRefs, resolved.unresolvedRefs);
|
|
3499
|
+
const roleType = this.getSemanticRoleType(groupKey);
|
|
3500
|
+
const preview = this.renderSemanticPreview(roleType, resolved.value);
|
|
3501
|
+
const aliasLine = directRefs.length > 0 ? `<div class="semantic-role__meta semantic-role__meta--alias"><span>value:</span> <code>${this.renderLinkedTokenValue(token.value)}</code></div>\n` : "";
|
|
3502
|
+
const references = this.renderSemanticReferences(token.value, tokenMap);
|
|
3503
|
+
const warnings = warningMessages.map((message) => `<span class="semantic-role__warning">${this.esc(message)}</span>`).join("");
|
|
3504
|
+
const plainClass = preview ? "" : " semantic-role--plain";
|
|
3505
|
+
return `<article class="semantic-role semantic-role--${this.esc(roleType)}${plainClass}" data-token-name="${this.esc(token.name)}">\n${preview}\n<div class="semantic-role__content">\n<div class="semantic-role__header"><div class="semantic-role__name">${this.esc(roleName)}</div>${warnings}</div>\n${aliasLine}${references}<div class="semantic-role__meta semantic-role__meta--resolved"><span>resolved:</span> <code>${this.esc(resolved.value || "unresolved")}</code></div>\n<div class="semantic-role__meta semantic-role__meta--role"><span>css:</span> <code class="semantic-role__token">${this.esc(token.name)}</code></div>\n</div>\n</article>\n`;
|
|
3506
|
+
}
|
|
3507
|
+
renderSemanticPreview(roleType, resolvedValue) {
|
|
3508
|
+
if (roleType === "color") {
|
|
3509
|
+
if (this.isRenderableColor(resolvedValue)) return `<span class="semantic-role__swatch" style="background:${this.esc(resolvedValue)}"></span>`;
|
|
3510
|
+
return `<span class="semantic-role__swatch semantic-role__swatch--empty"></span>`;
|
|
3511
|
+
}
|
|
3512
|
+
return "";
|
|
3513
|
+
}
|
|
3514
|
+
renderSemanticReferences(value, tokenMap) {
|
|
3515
|
+
const refs = this.extractVarReferences(value);
|
|
3516
|
+
if (refs.length > 1) return this.renderSemanticParts(refs);
|
|
3517
|
+
if (refs.length === 1) {
|
|
3518
|
+
const nestedRefs = this.extractVarReferences(tokenMap.get(refs[0]) ?? "");
|
|
3519
|
+
if (nestedRefs.length > 1) return this.renderSemanticParts(nestedRefs);
|
|
3520
|
+
}
|
|
3521
|
+
return "";
|
|
3522
|
+
}
|
|
3523
|
+
renderSemanticParts(refs) {
|
|
3524
|
+
return `<div class="semantic-role__parts"><span class="semantic-role__parts-label">parts:</span>${[...new Set(refs)].map((ref) => {
|
|
3525
|
+
const tokenPath = this.cssVariableToTokenPath(ref);
|
|
3526
|
+
return `<span><code>${this.esc(this.getCompositePartName(ref))}</code><a class="semantic-role__ref" href="#${this.esc(this.tokenId(ref))}" title="${this.esc(tokenPath)}">${this.esc(tokenPath)}</a></span>`;
|
|
3527
|
+
}).join("")}</div>\n`;
|
|
3528
|
+
}
|
|
3529
|
+
renderLinkedTokenValue(value) {
|
|
3530
|
+
const pattern = /var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]+)?\)/g;
|
|
3531
|
+
let html = "";
|
|
3532
|
+
let lastIndex = 0;
|
|
3533
|
+
for (const match of value.matchAll(pattern)) {
|
|
3534
|
+
const index = match.index ?? 0;
|
|
3535
|
+
html += this.esc(value.slice(lastIndex, index));
|
|
3536
|
+
const ref = match[1];
|
|
3537
|
+
const raw = match[0];
|
|
3538
|
+
if (!ref.startsWith("--primitive-")) html += this.esc(raw);
|
|
3539
|
+
else html += `<a class="semantic-role__ref" href="#${this.esc(this.tokenId(ref))}" title="${this.esc(ref)}">${this.esc(raw)}</a>`;
|
|
3540
|
+
lastIndex = index + raw.length;
|
|
3541
|
+
}
|
|
3542
|
+
html += this.esc(value.slice(lastIndex));
|
|
3543
|
+
return html;
|
|
3544
|
+
}
|
|
3545
|
+
getCompositePartName(ref) {
|
|
3546
|
+
const match = ref.replace(/^--/, "").match(/-(width|style|color|duration|delay|timingFunction-\d+|offsetX|offsetY|blur|spread|fontSize|fontWeight|lineHeight|letterSpacing|fontFamily-\d+)$/);
|
|
3547
|
+
if (!match) return "value";
|
|
3548
|
+
return match[1];
|
|
3549
|
+
}
|
|
3550
|
+
getSemanticWarnings(directRefs, unresolvedRefs) {
|
|
3551
|
+
const warnings = [];
|
|
3552
|
+
if (directRefs.some((ref) => this.cssVariableToTokenPath(ref).startsWith("component."))) warnings.push("warning: references component token");
|
|
3553
|
+
if (unresolvedRefs.length > 0) warnings.push("warning: unresolved reference");
|
|
3554
|
+
return warnings;
|
|
3555
|
+
}
|
|
3556
|
+
resolveTokenValue(value, tokenMap, stack = []) {
|
|
3557
|
+
const unresolvedRefs = [];
|
|
3558
|
+
return {
|
|
3559
|
+
value: value.replace(/var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]+)?\)/g, (_match, ref) => {
|
|
3560
|
+
const aggregate = this.resolveAggregateTokenValue(ref, tokenMap);
|
|
3561
|
+
if (aggregate) return aggregate;
|
|
3562
|
+
if (stack.includes(ref) || !tokenMap.has(ref)) {
|
|
3563
|
+
unresolvedRefs.push(ref);
|
|
3564
|
+
return `var(${ref})`;
|
|
3565
|
+
}
|
|
3566
|
+
const nested = this.resolveTokenValue(tokenMap.get(ref), tokenMap, [...stack, ref]);
|
|
3567
|
+
unresolvedRefs.push(...nested.unresolvedRefs);
|
|
3568
|
+
return nested.value;
|
|
3569
|
+
}).replace(/\s+/g, " ").trim(),
|
|
3570
|
+
unresolvedRefs
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
resolveAggregateTokenValue(ref, tokenMap) {
|
|
3574
|
+
if (tokenMap.has(ref)) return null;
|
|
3575
|
+
const typography = this.resolveAggregateTypographyTokenValue(ref, tokenMap);
|
|
3576
|
+
if (typography) return typography;
|
|
3577
|
+
const gradient = this.resolveAggregateGradientTokenValue(ref, tokenMap);
|
|
3578
|
+
if (gradient) return gradient;
|
|
3579
|
+
return null;
|
|
3580
|
+
}
|
|
3581
|
+
resolveAggregateGradientTokenValue(ref, tokenMap) {
|
|
3582
|
+
const prefix = `${ref}-`;
|
|
3583
|
+
const stops = this.collectGradientStops(prefix, tokenMap);
|
|
3584
|
+
if (stops.length === 0 || !ref.includes("-gradient-")) return null;
|
|
3585
|
+
return `linear-gradient(90deg, ${stops.map((stop) => `${stop.color} ${this.formatGradientPosition(stop.position)}`.trim()).join(", ")})`;
|
|
3586
|
+
}
|
|
3587
|
+
collectGradientStops(prefix, tokenMap) {
|
|
3588
|
+
const stops = /* @__PURE__ */ new Map();
|
|
3589
|
+
const pattern = new RegExp(`^${this.escapeRegExp(prefix)}(\\d+)-(color|position)$`);
|
|
3590
|
+
for (const [name, value] of tokenMap) {
|
|
3591
|
+
const match = name.match(pattern);
|
|
3592
|
+
if (!match) continue;
|
|
3593
|
+
const index = Number(match[1]);
|
|
3594
|
+
const stop = stops.get(index) ?? {};
|
|
3595
|
+
if (match[2] === "color") stop.color = value;
|
|
3596
|
+
else stop.position = value;
|
|
3597
|
+
stops.set(index, stop);
|
|
3598
|
+
}
|
|
3599
|
+
return [...stops.entries()].sort(([left], [right]) => left - right).map(([, stop]) => stop).filter((stop) => Boolean(stop.color));
|
|
3600
|
+
}
|
|
3601
|
+
resolveAggregateTypographyTokenValue(ref, tokenMap) {
|
|
3602
|
+
const prefix = `${ref}-`;
|
|
3603
|
+
if ([...tokenMap.keys()].filter((name) => name.startsWith(prefix)).length === 0 || !ref.includes("-typography-")) return null;
|
|
3604
|
+
const family = this.collectIndexedValues(prefix, "fontFamily", tokenMap).join(", ");
|
|
3605
|
+
const size = tokenMap.get(`${prefix}fontSize`);
|
|
3606
|
+
const weight = this.normalizeFontWeight(tokenMap.get(`${prefix}fontWeight`));
|
|
3607
|
+
const lineHeight = tokenMap.get(`${prefix}lineHeight`);
|
|
3608
|
+
const letterSpacing = tokenMap.get(`${prefix}letterSpacing`);
|
|
3609
|
+
const parts = [];
|
|
3610
|
+
if (weight) parts.push(weight);
|
|
3611
|
+
if (size) parts.push(lineHeight ? `${size}/${lineHeight}` : size);
|
|
3612
|
+
if (family) parts.push(family);
|
|
3613
|
+
if (parts.length === 0) return null;
|
|
3614
|
+
if (letterSpacing) return `${parts.join(" ")}; letter-spacing ${letterSpacing}`;
|
|
3615
|
+
return parts.join(" ");
|
|
3616
|
+
}
|
|
3617
|
+
collectIndexedValues(prefix, property, tokenMap) {
|
|
3618
|
+
const values = [];
|
|
3619
|
+
const pattern = new RegExp(`^${this.escapeRegExp(prefix + property)}-(\\d+)$`);
|
|
3620
|
+
for (const [name, value] of tokenMap) {
|
|
3621
|
+
const match = name.match(pattern);
|
|
3622
|
+
if (!match) continue;
|
|
3623
|
+
values.push({
|
|
3624
|
+
index: Number(match[1]),
|
|
3625
|
+
value
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
return values.sort((left, right) => left.index - right.index).map((item) => item.value);
|
|
3629
|
+
}
|
|
3630
|
+
normalizeFontWeight(value) {
|
|
3631
|
+
if (value === "regular") return "400";
|
|
3632
|
+
return value;
|
|
3633
|
+
}
|
|
3634
|
+
escapeRegExp(value) {
|
|
3635
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3636
|
+
}
|
|
3637
|
+
extractVarReferences(value) {
|
|
3638
|
+
return [...value.matchAll(/var\(\s*(--[a-zA-Z0-9_-]+)\s*(?:,[^)]+)?\)/g)].map((match) => match[1]);
|
|
3639
|
+
}
|
|
3640
|
+
createTokenMap(entries) {
|
|
3641
|
+
const map = /* @__PURE__ */ new Map();
|
|
3642
|
+
for (const entry of entries) if (!map.has(entry.name)) map.set(entry.name, entry.value);
|
|
3643
|
+
return map;
|
|
3644
|
+
}
|
|
3645
|
+
dedupeTokens(tokens) {
|
|
3646
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3647
|
+
const result = [];
|
|
3648
|
+
for (const token of tokens) {
|
|
3649
|
+
if (seen.has(token.name)) continue;
|
|
3650
|
+
seen.add(token.name);
|
|
3651
|
+
result.push(token);
|
|
3652
|
+
}
|
|
3653
|
+
return result;
|
|
3654
|
+
}
|
|
3655
|
+
cssVariableToTokenPath(name) {
|
|
3656
|
+
return name.replace(/^--/, "").replace(/-/g, ".");
|
|
3657
|
+
}
|
|
3658
|
+
getSemanticGroupKey(parts) {
|
|
3659
|
+
const prefix = parts.slice(1, 3).join(".");
|
|
3660
|
+
if (TokenHtmlShowcaseRenderer.#SEMANTIC_GROUP_TITLES.has(prefix)) return prefix;
|
|
3661
|
+
if (parts[1] === "text") return "text";
|
|
3662
|
+
if (parts[1] === "motion") return "motion";
|
|
3663
|
+
return "other";
|
|
3664
|
+
}
|
|
3665
|
+
getSemanticRoleName(path) {
|
|
3666
|
+
const parts = path.split(".");
|
|
3667
|
+
if (parts[0] !== "semantic") return path;
|
|
3668
|
+
if (parts[1] === "color" || parts[1] === "space" || parts[1] === "shape") return parts.slice(2).join(".");
|
|
3669
|
+
return parts.slice(1).join(".");
|
|
3670
|
+
}
|
|
3671
|
+
getSemanticRoleType(groupKey) {
|
|
3672
|
+
if (groupKey.startsWith("color.")) return "color";
|
|
3673
|
+
if (groupKey.startsWith("space.")) return "spacing";
|
|
3674
|
+
if (groupKey === "shape.radius") return "radius";
|
|
3675
|
+
if (groupKey === "text") return "text";
|
|
3676
|
+
if (groupKey === "motion") return "motion";
|
|
3677
|
+
return "other";
|
|
3678
|
+
}
|
|
3679
|
+
isRenderableColor(value) {
|
|
3680
|
+
const normalized = value.trim();
|
|
3681
|
+
return TokenHtmlShowcaseRenderer.#COLOR_HEX_RE.test(normalized) || /^(rgb|rgba|hsl|hsla|oklch|oklab|lab|color)\(/i.test(normalized) || normalized === "transparent" || normalized === "currentColor";
|
|
3682
|
+
}
|
|
3683
|
+
renderGroupedTokens(tokens, idPrefix) {
|
|
3684
|
+
let html = "";
|
|
3685
|
+
for (const groupInfo of this.getDisplayGroups(tokens)) {
|
|
3686
|
+
html += `<section class="token-subgroup">\n`;
|
|
3687
|
+
html += `<h3 class="token-subgroup-title" id="${this.esc(this.sectionId(`${idPrefix}-group-title`, groupInfo.key))}">${this.esc(groupInfo.title)}</h3>\n`;
|
|
3688
|
+
const listClass = groupInfo.group === "fonts" || groupInfo.group === "typography" || groupInfo.kind === "fontCollection" ? "token-list token-list--font" : "token-list";
|
|
3689
|
+
html += `<div class="${listClass}">\n`;
|
|
3690
|
+
html += groupInfo.kind === "fontCollection" ? groupInfo.tokens.map((entry) => this.renderFontVariantCard(this.toFontVariantCard(groupInfo.key, entry))).join("") : this.renderGroupItems(groupInfo.group, groupInfo.tokens);
|
|
3691
|
+
html += `</div>\n`;
|
|
3692
|
+
html += `</section>\n`;
|
|
3693
|
+
}
|
|
3694
|
+
return html;
|
|
3695
|
+
}
|
|
3696
|
+
getDisplayGroups(tokens) {
|
|
3697
|
+
const grouped = this.#classifier.groupTokens(tokens);
|
|
3698
|
+
const groups = [];
|
|
3699
|
+
for (const [group, groupTokens] of grouped) if (group === "fonts") groups.push(...this.#fontAggregator.aggregate(groupTokens).map((collection) => ({
|
|
3700
|
+
key: collection.key,
|
|
3701
|
+
title: collection.title,
|
|
3702
|
+
group,
|
|
3703
|
+
tokens: collection.entries,
|
|
3704
|
+
kind: "fontCollection"
|
|
3705
|
+
})));
|
|
3706
|
+
else groups.push({
|
|
3707
|
+
key: group,
|
|
3708
|
+
title: group,
|
|
3709
|
+
group,
|
|
3710
|
+
tokens: groupTokens,
|
|
3711
|
+
kind: "tokens"
|
|
3712
|
+
});
|
|
3713
|
+
return groups.sort((left, right) => left.title.localeCompare(right.title));
|
|
3714
|
+
}
|
|
3715
|
+
renderMenuGroupLinks(idPrefix, tokens) {
|
|
3716
|
+
return this.getDisplayGroups(tokens).map((group) => {
|
|
3717
|
+
const subId = this.sectionId(`${idPrefix}-group-title`, group.key);
|
|
3718
|
+
return `<a class="sidebar-sub-link" href="#${this.esc(subId)}">${this.esc(group.title)}</a>`;
|
|
3719
|
+
}).join("\n");
|
|
3720
|
+
}
|
|
3721
|
+
sectionId(prefix, value) {
|
|
3722
|
+
return `${prefix}-${value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")}`;
|
|
3723
|
+
}
|
|
3724
|
+
tokenId(name) {
|
|
3725
|
+
return this.sectionId("token", name.replace(/^--/, ""));
|
|
3726
|
+
}
|
|
3727
|
+
renderGroupItems(group, tokens) {
|
|
3728
|
+
if (group === "borders") return this.renderBorderItems(tokens);
|
|
3729
|
+
if (group === "typography") return this.renderTypographyItems(tokens);
|
|
3730
|
+
if (group === "shadows") return this.renderShadowItems(tokens);
|
|
3731
|
+
if (group === "gradients") return this.renderGradientItems(tokens);
|
|
3732
|
+
if (group === "motion") return this.renderMotionItems(tokens);
|
|
3733
|
+
return tokens.map((token) => this.renderTokenItem(group, token.name, token.value)).join("");
|
|
3734
|
+
}
|
|
3735
|
+
renderBorderItems(tokens) {
|
|
3736
|
+
const aggregated = this.#borderAggregator.aggregate(tokens);
|
|
3737
|
+
return [
|
|
3738
|
+
...aggregated.borders.map((item) => this.renderBorderItem(item)),
|
|
3739
|
+
...aggregated.borderWidths.map((item) => this.renderBorderWidthItem(item)),
|
|
3740
|
+
...aggregated.strokeStyles.map((item) => this.renderStrokeStyleItem(item)),
|
|
3741
|
+
...aggregated.fallback.map((token) => this.renderTokenItem("borders", token.name, token.value))
|
|
3742
|
+
].join("");
|
|
3743
|
+
}
|
|
3744
|
+
renderTypographyItems(tokens) {
|
|
3745
|
+
return this.#typographyAggregator.aggregate(tokens).map((item) => this.renderTokenItem("typography", item.name, item.value, item.typography)).join("");
|
|
3746
|
+
}
|
|
3747
|
+
renderShadowItems(tokens) {
|
|
3748
|
+
const aggregated = this.#shadowAggregator.aggregate(tokens);
|
|
3749
|
+
return [...aggregated.items.map((item) => this.renderShadowItem(item)), ...aggregated.fallback.map((token) => this.renderTokenItem("shadows", token.name, token.value))].join("");
|
|
3750
|
+
}
|
|
3751
|
+
renderGradientItems(tokens) {
|
|
3752
|
+
const aggregated = this.#gradientAggregator.aggregate(tokens);
|
|
3753
|
+
return [...aggregated.items.map((item) => this.renderGradientItem(item)), ...aggregated.fallback.map((token) => this.renderTokenItem("gradients", token.name, token.value))].join("");
|
|
3754
|
+
}
|
|
3755
|
+
renderMotionItems(tokens) {
|
|
3756
|
+
const aggregated = this.#transitionAggregator.aggregate(tokens);
|
|
3757
|
+
return [...aggregated.items.map((item) => this.renderTransitionItem(item)), ...aggregated.fallback.map((token) => this.renderMotionTokenItem(token.name, token.value))].join("");
|
|
3758
|
+
}
|
|
3759
|
+
getFontCollectionEntryLabel(name, key) {
|
|
3760
|
+
const normalized = name.replace(/^--/, "");
|
|
3761
|
+
return normalized.match(new RegExp(`${key}-([a-z0-9-]+)$`, "i"))?.[1] ?? normalized;
|
|
3762
|
+
}
|
|
3763
|
+
toFontVariantCard(key, entry) {
|
|
3764
|
+
const label = this.getFontCollectionEntryLabel(entry.name, key);
|
|
3765
|
+
const title = this.toTitleCase(label.replace(/-/g, " "));
|
|
3766
|
+
const metaLabel = this.getFontCollectionMetaLabel(key);
|
|
3767
|
+
return {
|
|
3768
|
+
name: title,
|
|
3769
|
+
value: entry.value,
|
|
3770
|
+
previewStyle: this.getFontVariantPreviewStyle(key, entry.value),
|
|
3771
|
+
metaLabel
|
|
3772
|
+
};
|
|
3773
|
+
}
|
|
3774
|
+
getFontCollectionMetaLabel(key) {
|
|
3775
|
+
if (key === "font-family") return "Font";
|
|
3776
|
+
if (key === "font-weight") return "Weight";
|
|
3777
|
+
if (key === "font-size") return "Size";
|
|
3778
|
+
if (key === "line-height") return "Line height";
|
|
3779
|
+
if (key === "letter-spacing") return "Letter spacing";
|
|
3780
|
+
return "Value";
|
|
3781
|
+
}
|
|
3782
|
+
getFontVariantPreviewStyle(key, value) {
|
|
3783
|
+
const base = [
|
|
3784
|
+
"font-family: Inter, Arial, sans-serif",
|
|
3785
|
+
"font-size: 24px",
|
|
3786
|
+
"line-height: 1.6",
|
|
3787
|
+
"font-weight: 400",
|
|
3788
|
+
"letter-spacing: 0"
|
|
3789
|
+
];
|
|
3790
|
+
if (key === "font-family") base[0] = `font-family: ${this.esc(value)}`;
|
|
3791
|
+
else if (key === "font-weight") base[3] = `font-weight: ${this.esc(value)}`;
|
|
3792
|
+
else if (key === "font-size") base[1] = `font-size: ${this.esc(value)}`;
|
|
3793
|
+
else if (key === "line-height") base[2] = `line-height: ${this.esc(value)}`;
|
|
3794
|
+
else if (key === "letter-spacing") base[4] = `letter-spacing: ${this.esc(value)}`;
|
|
3795
|
+
else return "";
|
|
3796
|
+
return base.join(";");
|
|
3797
|
+
}
|
|
3798
|
+
renderFontVariantCard(card) {
|
|
3799
|
+
return `<div class="token-item token-item--font-card">${this.renderGroupBadge("fonts")}<div class="font-preview font-preview--grid" style="${card.previewStyle ?? ""}">${this.esc(TokenHtmlShowcaseRenderer.#FONT_PREVIEW_TEXT)}</div><span class="token-name token-name--font-card">${this.esc(card.name)}</span><div class="token-meta"><span><b>${this.esc(card.metaLabel)}:</b> <span class="token-meta-value">${this.esc(card.value)}</span></span></div></div>\n`;
|
|
3800
|
+
}
|
|
3801
|
+
renderBorderItem(item) {
|
|
3802
|
+
const badge = this.renderGroupBadge("borders");
|
|
3803
|
+
const title = this.toTitleCase(item.name.replace(/^.*-border-/, "").replace(/-/g, " "));
|
|
3804
|
+
const color = item.color ?? "currentColor";
|
|
3805
|
+
const width = item.width ?? "1px";
|
|
3806
|
+
const style = item.style ?? "solid";
|
|
3807
|
+
const resolved = `${width} ${style} ${color}`;
|
|
3808
|
+
const previewStyle = `border:${this.esc(resolved)}`;
|
|
3809
|
+
const value = item.value ? this.renderMetaLine("value", item.value) : "";
|
|
3810
|
+
const resolvedLine = this.renderMetaLine("resolved", resolved);
|
|
3811
|
+
return `<div class="token-item token-item--border-card" id="${this.esc(this.tokenId(`--${item.name}`))}">${badge}<div class="border-preview border-preview--grid"><div class="border-preview-box" style="${previewStyle}"></div></div><span class="token-name token-name--font-card">${this.esc(title)}</span><div class="token-meta token-meta--stack">${value}${resolvedLine}<span><b>Color:</b> <span class="token-meta-value">${this.esc(color)}</span></span><span><b>Width:</b> <span class="token-meta-value">${this.esc(width)}</span></span><span><b>Style:</b> <span class="token-meta-value">${this.esc(style)}</span></span></div></div>\n`;
|
|
3812
|
+
}
|
|
3813
|
+
renderShadowItem(item) {
|
|
3814
|
+
const badge = this.renderGroupBadge("shadows");
|
|
3815
|
+
const title = this.toTitleCase(item.name.replace(/^.*-shadow-/, "").replace(/-/g, " "));
|
|
3816
|
+
const layers = item.layers.map((layer) => this.formatShadowLayer(layer));
|
|
3817
|
+
const boxShadow = layers.join(", ");
|
|
3818
|
+
const value = item.value ? this.renderMetaLine("value", item.value) : "";
|
|
3819
|
+
const resolved = this.renderMetaLine("resolved", boxShadow);
|
|
3820
|
+
const details = layers.map((layer, index) => `<span class="shadow-layer"><b>Layer ${index + 1}:</b> <span class="token-meta-value">${this.esc(layer)}</span></span>`).join("");
|
|
3821
|
+
return `<div class="token-item token-item--shadow-card" id="${this.esc(this.tokenId(`--${item.name}`))}" data-token-name="${this.esc(`--${item.name}`)}">${badge}<div class="shadow-preview shadow-preview--grid"><div class="shadow-preview-box" style="box-shadow:${this.esc(boxShadow)}"></div></div><span class="token-name token-name--font-card">${this.esc(title)}</span><div class="token-meta token-meta--stack">${value}${resolved}${details}</div></div>\n`;
|
|
3822
|
+
}
|
|
3823
|
+
renderGradientItem(item) {
|
|
3824
|
+
const badge = this.renderGroupBadge("gradients");
|
|
3825
|
+
const title = this.toTitleCase(item.name.replace(/^.*-gradient-/, "").replace(/-/g, " "));
|
|
3826
|
+
const gradient = this.formatGradientValue(item);
|
|
3827
|
+
const value = item.value ? this.renderMetaLine("value", item.value) : "";
|
|
3828
|
+
const resolved = this.renderMetaLine("resolved", gradient);
|
|
3829
|
+
const stops = this.renderGradientStops(item);
|
|
3830
|
+
return `<div class="token-item token-item--gradient-card" id="${this.esc(this.tokenId(`--${item.name}`))}" data-token-name="${this.esc(`--${item.name}`)}">${badge}<div class="token-swatch" style="background:${this.esc(gradient)}"></div><span class="token-name">${this.esc(title)}</span>${stops}<div class="token-meta token-meta--stack">${value}${resolved}</div></div>\n`;
|
|
3831
|
+
}
|
|
3832
|
+
renderGradientStops(item) {
|
|
3833
|
+
if (item.stops.length === 0) return "";
|
|
3834
|
+
const stops = item.stops.map((stop) => {
|
|
3835
|
+
const color = stop.color ?? "transparent";
|
|
3836
|
+
const position = this.formatGradientPosition(stop.position);
|
|
3837
|
+
const label = position ? `${color} ${position}` : color;
|
|
3838
|
+
return `<span class="gradient-stop"><span class="gradient-stop__swatch token-swatch" style="background:${this.esc(color)}"></span><span class="gradient-stop__value">${this.esc(label)}</span></span>`;
|
|
3839
|
+
}).join("");
|
|
3840
|
+
return `<div class="gradient-stops" aria-label="${this.esc(item.name)} gradient colors">${stops}</div>`;
|
|
3841
|
+
}
|
|
3842
|
+
renderTransitionItem(item) {
|
|
3843
|
+
const title = this.toTitleCase(item.name.replace(/^.*-transition-/, "").replace(/-/g, " "));
|
|
3844
|
+
const duration = item.duration ?? this.extractDurationValue(item.value) ?? "1200ms";
|
|
3845
|
+
const delay = item.delay ?? "0ms";
|
|
3846
|
+
const timing = this.formatTransitionTiming(item.timingFunction, item.value);
|
|
3847
|
+
const resolved = Boolean(item.duration || item.delay || item.timingFunction.length > 0) ? `${duration} ${timing} ${delay}` : void 0;
|
|
3848
|
+
return this.renderMotionCard({
|
|
3849
|
+
name: `--${item.name}`,
|
|
3850
|
+
title,
|
|
3851
|
+
duration,
|
|
3852
|
+
delay,
|
|
3853
|
+
timing,
|
|
3854
|
+
value: item.value,
|
|
3855
|
+
resolved
|
|
3856
|
+
});
|
|
3857
|
+
}
|
|
3858
|
+
renderMotionTokenItem(name, value) {
|
|
3859
|
+
const title = this.toMotionTitle(name);
|
|
3860
|
+
const duration = this.getMotionPreviewDuration(name, value);
|
|
3861
|
+
const timing = this.getMotionPreviewTiming(name, value);
|
|
3862
|
+
const resolved = this.getMotionResolvedValue(name, value, duration, timing, "0ms");
|
|
3863
|
+
return this.renderMotionCard({
|
|
3864
|
+
name,
|
|
3865
|
+
title,
|
|
3866
|
+
duration,
|
|
3867
|
+
delay: "0ms",
|
|
3868
|
+
timing,
|
|
3869
|
+
value,
|
|
3870
|
+
resolved
|
|
3871
|
+
});
|
|
3872
|
+
}
|
|
3873
|
+
renderMotionCard(info) {
|
|
3874
|
+
const badge = this.renderGroupBadge("motion");
|
|
3875
|
+
const style = [
|
|
3876
|
+
`--motion-duration:${this.esc(this.normalizeMotionDuration(info.duration))}`,
|
|
3877
|
+
`--motion-delay:${this.esc(info.delay)}`,
|
|
3878
|
+
`--motion-timing:${this.esc(info.timing)}`
|
|
3879
|
+
].join(";");
|
|
3880
|
+
const value = info.value ? `<span><b>value:</b> <span class="token-meta-value">${this.esc(info.value)}</span></span>` : "";
|
|
3881
|
+
const resolved = info.resolved && info.resolved !== info.value ? this.renderMetaLine("resolved", info.resolved) : "";
|
|
3882
|
+
return `<div class="token-item token-item--motion-card" id="${this.esc(this.tokenId(info.name))}" data-token-name="${this.esc(info.name)}">${badge}<div class="motion-preview motion-preview--grid" style="${style}"><div class="motion-track"><div class="motion-dot"></div></div></div><span class="token-name token-name--font-card">${this.esc(info.title)}</span><div class="token-meta token-meta--stack"><span><b>Duration:</b> <span class="token-meta-value">${this.esc(info.duration)}</span></span><span><b>Delay:</b> <span class="token-meta-value">${this.esc(info.delay)}</span></span><span><b>Timing:</b> <span class="token-meta-value">${this.esc(info.timing)}</span></span>${value}${resolved}</div></div>\n`;
|
|
3883
|
+
}
|
|
3884
|
+
renderMetaLine(label, value) {
|
|
3885
|
+
return `<span><b>${this.esc(label)}:</b> <span class="token-meta-value">${this.esc(value)}</span></span>`;
|
|
3886
|
+
}
|
|
3887
|
+
formatGradientValue(item) {
|
|
3888
|
+
if (item.stops.length === 0 && item.value) return item.value;
|
|
3889
|
+
return `linear-gradient(90deg, ${item.stops.map((stop) => `${stop.color ?? "transparent"} ${this.formatGradientPosition(stop.position)}`).join(", ")})`;
|
|
3890
|
+
}
|
|
3891
|
+
formatGradientPosition(position) {
|
|
3892
|
+
if (!position) return "";
|
|
3893
|
+
if (/^-?\d+(?:\.\d+)?$/.test(position)) return `${Number(position) * 100}%`;
|
|
3894
|
+
return position;
|
|
3895
|
+
}
|
|
3896
|
+
formatTransitionTiming(timingFunction, value) {
|
|
3897
|
+
const values = timingFunction.filter((part) => part !== void 0);
|
|
3898
|
+
if (values.length === 4) return `cubic-bezier(${values.join(", ")})`;
|
|
3899
|
+
if (value?.includes("cubic-bezier")) return value.match(/cubic-bezier\([^)]+\)/)?.[0] ?? "ease";
|
|
3900
|
+
return "ease";
|
|
3901
|
+
}
|
|
3902
|
+
extractDurationValue(value) {
|
|
3903
|
+
if (!value) return null;
|
|
3904
|
+
return value.match(/\b\d+(?:\.\d+)?m?s\b/)?.[0] ?? null;
|
|
3905
|
+
}
|
|
3906
|
+
getMotionPreviewDuration(name, value) {
|
|
3907
|
+
if (name.toLowerCase().includes("duration") && /^\d+(?:\.\d+)?m?s$/.test(value)) return value;
|
|
3908
|
+
return this.extractDurationValue(value) ?? "1200ms";
|
|
3909
|
+
}
|
|
3910
|
+
getMotionPreviewTiming(name, value) {
|
|
3911
|
+
if (name.toLowerCase().includes("cubic-bezier") && /^-?\d/.test(value)) return `cubic-bezier(${value})`;
|
|
3912
|
+
if (value.includes("cubic-bezier")) return value.match(/cubic-bezier\([^)]+\)/)?.[0] ?? "ease";
|
|
3913
|
+
return "ease";
|
|
3914
|
+
}
|
|
3915
|
+
getMotionResolvedValue(name, value, duration, timing, delay) {
|
|
3916
|
+
if (name.toLowerCase().includes("cubic-bezier") && /^-?\d/.test(value)) return timing;
|
|
3917
|
+
if (value.includes("cubic-bezier")) return `${duration} ${timing} ${delay}`;
|
|
3918
|
+
}
|
|
3919
|
+
normalizeMotionDuration(duration) {
|
|
3920
|
+
if (duration === "0ms" || duration === "0s" || duration === "0") return "1ms";
|
|
3921
|
+
return duration;
|
|
3922
|
+
}
|
|
3923
|
+
toMotionTitle(name) {
|
|
3924
|
+
return this.toTitleCase(name.replace(/^--/, "").replace(/^.*-(duration|cubic-bezier|transition)-/, "").replace(/-/g, " "));
|
|
3925
|
+
}
|
|
3926
|
+
formatShadowLayer(layer) {
|
|
3927
|
+
return [
|
|
3928
|
+
layer.offsetX ?? "0px",
|
|
3929
|
+
layer.offsetY ?? "0px",
|
|
3930
|
+
layer.blur ?? "0px",
|
|
3931
|
+
layer.spread ?? "0px",
|
|
3932
|
+
this.formatShadowColor(layer.color ?? "currentColor")
|
|
3933
|
+
].join(" ");
|
|
3934
|
+
}
|
|
3935
|
+
formatShadowColor(color) {
|
|
3936
|
+
const match = color.match(/^#([0-9a-fA-F]{8})$/);
|
|
3937
|
+
if (!match) return color;
|
|
3938
|
+
const hex = match[1];
|
|
3939
|
+
const red = Number.parseInt(hex.slice(0, 2), 16);
|
|
3940
|
+
const green = Number.parseInt(hex.slice(2, 4), 16);
|
|
3941
|
+
const blue = Number.parseInt(hex.slice(4, 6), 16);
|
|
3942
|
+
const alpha = Number.parseInt(hex.slice(6, 8), 16);
|
|
3943
|
+
return `rgb(${red} ${green} ${blue} / ${Number((alpha / 255).toFixed(2))})`;
|
|
3944
|
+
}
|
|
3945
|
+
renderBorderWidthItem(item) {
|
|
3946
|
+
return this.renderDimensionItem("borders", item.name, item.value);
|
|
3947
|
+
}
|
|
3948
|
+
renderStrokeStyleItem(item) {
|
|
3949
|
+
const badge = this.renderGroupBadge("borders");
|
|
3950
|
+
const title = this.toTitleCase(item.name.replace(/^.*-stroke-style-/, "").replace(/-/g, " "));
|
|
3951
|
+
const style = item.value ?? (item.dashArray.length > 0 ? "dashed" : "solid");
|
|
3952
|
+
const previewStyle = `border:2px ${this.esc(style)} #334155`;
|
|
3953
|
+
const dashArray = item.dashArray.filter(Boolean).join(", ");
|
|
3954
|
+
return `<div class="token-item token-item--border-card" id="${this.esc(this.tokenId(`--${item.name}`))}">${badge}<div class="border-preview border-preview--grid"><div class="border-preview-box" style="${previewStyle}"></div></div><span class="token-name token-name--font-card">${this.esc(title)}</span><div class="token-meta token-meta--stack"><span><b>Style:</b> <span class="token-meta-value">${this.esc(style)}</span></span>${dashArray ? `<span><b>Dash:</b> <span class="token-meta-value">${this.esc(dashArray)}</span></span>` : ""}${item.lineCap ? `<span><b>Line cap:</b> <span class="token-meta-value">${this.esc(item.lineCap)}</span></span>` : ""}</div></div>\n`;
|
|
3955
|
+
}
|
|
3956
|
+
renderDimensionItem(group, name, value) {
|
|
3957
|
+
const badge = this.renderGroupBadge(group);
|
|
3958
|
+
const title = this.toDimensionTitle(name);
|
|
3959
|
+
const markerStyle = this.getDimensionMarkerStyle(value);
|
|
3960
|
+
return `<div class="token-item token-item--dimension-card" id="${this.esc(this.tokenId(name))}" data-token-name="${this.esc(name)}">${badge}<div class="dimension-preview dimension-preview--grid"><div class="dimension-marker" style="${markerStyle}"></div></div><span class="token-name token-name--font-card">${this.esc(title)}</span><div class="token-meta"><span><b>Dimension:</b> <span class="token-meta-value">${this.esc(value)}</span></span></div></div>\n`;
|
|
3961
|
+
}
|
|
3962
|
+
renderRadiusItem(name, value) {
|
|
3963
|
+
const badge = this.renderGroupBadge("radius");
|
|
3964
|
+
const title = this.toDimensionTitle(name);
|
|
3965
|
+
const radius = this.esc(value);
|
|
3966
|
+
return `<div class="token-item token-item--radius-card" id="${this.esc(this.tokenId(name))}" data-token-name="${this.esc(name)}">${badge}<div class="radius-preview radius-preview--grid"><div class="radius-preview-box" style="border-radius:${radius}"></div></div><span class="token-name token-name--font-card">${this.esc(title)}</span><div class="token-meta"><span><b>Radius:</b> <span class="token-meta-value">${this.esc(value)}</span></span></div></div>\n`;
|
|
3967
|
+
}
|
|
3968
|
+
renderOpacityItem(name, value) {
|
|
3969
|
+
const badge = this.renderGroupBadge("opacity");
|
|
3970
|
+
const title = this.toTitleCase(name.replace(/^--/, "").replace(/^.*-opacity-/, "").replace(/^.*-alpha-/, "").replace(/-/g, " "));
|
|
3971
|
+
const opacity = this.parseOpacityValue(value);
|
|
3972
|
+
const previewOpacity = opacity ?? 1;
|
|
3973
|
+
const resolved = opacity !== null ? this.renderMetaLine("resolved", `${Math.round(opacity * 100)}%`) : "";
|
|
3974
|
+
return `<div class="token-item token-item--opacity-card" id="${this.esc(this.tokenId(name))}" data-token-name="${this.esc(name)}">${badge}<div class="opacity-preview opacity-preview--checker"><div class="opacity-preview-fill" style="opacity:${this.esc(String(previewOpacity))}"></div></div><span class="token-name token-name--font-card">${this.esc(title)}</span><div class="token-meta token-meta--stack">${this.renderMetaLine("value", value)}${resolved}</div></div>\n`;
|
|
3975
|
+
}
|
|
3976
|
+
parseOpacityValue(value) {
|
|
3977
|
+
const normalized = value.trim();
|
|
3978
|
+
if (/^\d+(?:\.\d+)?%$/.test(normalized)) return Math.max(0, Math.min(1, Number.parseFloat(normalized) / 100));
|
|
3979
|
+
if (/^\d+(?:\.\d+)?$/.test(normalized)) return Math.max(0, Math.min(1, Number.parseFloat(normalized)));
|
|
3980
|
+
return null;
|
|
3981
|
+
}
|
|
3982
|
+
toDimensionTitle(name) {
|
|
3983
|
+
return this.toTitleCase(name.replace(/^--/, "").replace(/^.*-dimension-/, "").replace(/-/g, " "));
|
|
3984
|
+
}
|
|
3985
|
+
getDimensionMarkerStyle(value) {
|
|
3986
|
+
if (value === "0px" || value === "0") return "width:1px;opacity:0.25";
|
|
3987
|
+
return `width:${this.esc(value)}`;
|
|
3988
|
+
}
|
|
3989
|
+
toTitleCase(value) {
|
|
3990
|
+
return value.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
3991
|
+
}
|
|
3992
|
+
renderTokenItem(group, name, value, typography) {
|
|
3993
|
+
const lowerName = name.toLowerCase();
|
|
3994
|
+
let preview = "";
|
|
3995
|
+
let details = "";
|
|
3996
|
+
if (group === "colors" || TokenHtmlShowcaseRenderer.#COLOR_HEX_RE.test(value) || value.startsWith("rgb") || value.startsWith("hsl")) preview = `<div class="token-swatch" style="background:${value}"></div>`;
|
|
3997
|
+
else if (group === "gradients") {
|
|
3998
|
+
preview = `<div class="token-swatch" style="background:${this.esc(value)}"></div>`;
|
|
3999
|
+
details = this.renderGradientStopsFromValue(name, value);
|
|
4000
|
+
} else if (group === "fonts" || group === "typography") {
|
|
4001
|
+
const typographyInfo = typography ?? this.extractTypographyInfo(name, value);
|
|
4002
|
+
const styleParts = this.getTypographyStyleParts(typographyInfo);
|
|
4003
|
+
if (group === "typography" && styleParts.length === 0) styleParts.push(`font:${this.esc(value)}`);
|
|
4004
|
+
preview = `<div class="font-preview" style="${styleParts.join(";")}">${this.esc(TokenHtmlShowcaseRenderer.#FONT_PREVIEW_TEXT)}</div>`;
|
|
4005
|
+
details = `<div class="token-meta"><span><b>value:</b> <span class="token-meta-value">${this.esc(value)}</span></span></div>`;
|
|
4006
|
+
} else if (group === "radius") return this.renderRadiusItem(name, value);
|
|
4007
|
+
else if (group === "opacity") return this.renderOpacityItem(name, value);
|
|
4008
|
+
else if (group === "spacing" || group === "sizes") return this.renderDimensionItem(group, name, value);
|
|
4009
|
+
else if (lowerName.includes("shadow")) preview = `<div class="shadow-visual" style="box-shadow:${value}"></div>`;
|
|
4010
|
+
else if (lowerName.includes("border") || lowerName.includes("stroke")) preview = `<div class="border-visual" style="border:${value}"></div>`;
|
|
4011
|
+
else if (lowerName.includes("gradient")) preview = `<div class="gradient-visual" style="background:${value}"></div>`;
|
|
4012
|
+
else if (lowerName.startsWith("--font-")) {
|
|
4013
|
+
const prop = lowerName.slice(2).replace(/-/g, "-");
|
|
4014
|
+
preview = `<div class="font-sample" style="${this.esc(prop)}:${this.esc(value)}">Aa</div>`;
|
|
4015
|
+
} else if (group === "motion") return this.renderMotionTokenItem(name, value);
|
|
4016
|
+
const badge = this.renderGroupBadge(group);
|
|
4017
|
+
const valueLine = group === "fonts" || group === "typography" ? "" : `<div class="token-meta"><span><b>value:</b> <span class="token-meta-value">${this.esc(value)}</span></span></div>`;
|
|
4018
|
+
return `<div class="token-item${group === "colors" ? " token-item--color-card" : group === "gradients" ? " token-item--gradient-card" : ""}" id="${this.esc(this.tokenId(name))}">${badge}${preview}<span class="token-name">${this.esc(name)}</span>${details}${valueLine}</div>\n`;
|
|
4019
|
+
}
|
|
4020
|
+
renderGradientStopsFromValue(name, value) {
|
|
4021
|
+
const stops = [...value.matchAll(/(#[0-9a-fA-F]{3,8})\s*(-?\d+(?:\.\d+)?%?)?/g)].map((match) => ({
|
|
4022
|
+
color: match[1],
|
|
4023
|
+
position: match[2]
|
|
4024
|
+
}));
|
|
4025
|
+
if (stops.length === 0) return "";
|
|
4026
|
+
const item = {
|
|
4027
|
+
name: name.replace(/^--/, ""),
|
|
4028
|
+
value,
|
|
4029
|
+
stops
|
|
4030
|
+
};
|
|
4031
|
+
return this.renderGradientStops(item);
|
|
4032
|
+
}
|
|
4033
|
+
renderGroupBadge(group) {
|
|
4034
|
+
if (!new Set([
|
|
4035
|
+
"colors",
|
|
4036
|
+
"fonts",
|
|
4037
|
+
"typography",
|
|
4038
|
+
"borders",
|
|
4039
|
+
"radius",
|
|
4040
|
+
"spacing",
|
|
4041
|
+
"shadows",
|
|
4042
|
+
"gradients",
|
|
4043
|
+
"motion",
|
|
4044
|
+
"opacity",
|
|
4045
|
+
"sizes",
|
|
4046
|
+
"other"
|
|
4047
|
+
]).has(group)) return "";
|
|
4048
|
+
return `<span class="token-type-badge">${this.esc(group)}</span>`;
|
|
4049
|
+
}
|
|
4050
|
+
getTypographyStyleParts(typographyInfo) {
|
|
4051
|
+
const styleParts = [];
|
|
4052
|
+
if (typographyInfo.family) styleParts.push(`font-family:${this.esc(typographyInfo.family)}`);
|
|
4053
|
+
if (typographyInfo.size) styleParts.push(`font-size:${this.esc(typographyInfo.size)}`);
|
|
4054
|
+
if (typographyInfo.weight) styleParts.push(`font-weight:${this.esc(typographyInfo.weight)}`);
|
|
4055
|
+
if (typographyInfo.lineHeight) styleParts.push(`line-height:${this.esc(typographyInfo.lineHeight)}`);
|
|
4056
|
+
if (typographyInfo.letterSpacing) styleParts.push(`letter-spacing:${this.esc(typographyInfo.letterSpacing)}`);
|
|
4057
|
+
return styleParts;
|
|
4058
|
+
}
|
|
4059
|
+
extractTypographyInfo(name, value) {
|
|
4060
|
+
const lowerName = name.toLowerCase();
|
|
4061
|
+
const info = {};
|
|
4062
|
+
if (lowerName.includes("family")) info.family = value;
|
|
4063
|
+
if (lowerName.includes("size")) info.size = value;
|
|
4064
|
+
if (lowerName.includes("weight")) info.weight = value;
|
|
4065
|
+
if (lowerName.includes("line-height") || lowerName.includes("lineheight")) info.lineHeight = value;
|
|
4066
|
+
if (lowerName.includes("letter-spacing") || lowerName.includes("tracking")) info.letterSpacing = value;
|
|
4067
|
+
const shortMatch = value.match(/(\d+(?:\.\d+)?(?:px|rem|em|%)?)(?:\/([^\s]+))?\s+(.+)$/);
|
|
4068
|
+
if (shortMatch) {
|
|
4069
|
+
info.size ??= shortMatch[1];
|
|
4070
|
+
info.lineHeight ??= shortMatch[2];
|
|
4071
|
+
info.family ??= shortMatch[3].trim();
|
|
4072
|
+
}
|
|
4073
|
+
const weightMatch = value.match(/\b([1-9]00|normal|bold|lighter|bolder)\b/i);
|
|
4074
|
+
if (weightMatch) info.weight ??= weightMatch[1];
|
|
4075
|
+
return info;
|
|
4076
|
+
}
|
|
4077
|
+
esc(str) {
|
|
4078
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4079
|
+
}
|
|
4080
|
+
};
|
|
4081
|
+
//#endregion
|
|
4082
|
+
//#region src/core/showcase/TokenHtmlShowcaseBuilder.ts
|
|
4083
|
+
/**
|
|
4084
|
+
* Builds an HTML showcase from JSON tokens or ready CSS.
|
|
4085
|
+
*
|
|
4086
|
+
* @remarks
|
|
4087
|
+
* Implements the common showcase pipeline and gives validation, conversion,
|
|
4088
|
+
* CSS parsing and HTML rendering to separate classes.
|
|
4089
|
+
*/
|
|
4090
|
+
var TokenHtmlShowcaseBuilder = class {
|
|
4091
|
+
#validator;
|
|
4092
|
+
#converter;
|
|
4093
|
+
#parser;
|
|
4094
|
+
#renderer;
|
|
4095
|
+
constructor(validator, converter, parser = new CssTokenParser(), renderer = new TokenHtmlShowcaseRenderer()) {
|
|
4096
|
+
this.#validator = validator;
|
|
4097
|
+
this.#converter = converter;
|
|
4098
|
+
this.#parser = parser;
|
|
4099
|
+
this.#renderer = renderer;
|
|
4100
|
+
}
|
|
4101
|
+
async showcase(sources) {
|
|
4102
|
+
if (sources.length === 0) throw new Error("No token sources provided");
|
|
4103
|
+
if (sources.length === 1) {
|
|
4104
|
+
const sourceContent = await readFile(sources[0], "utf8");
|
|
4105
|
+
if (this.#isCssContent(sourceContent)) return this.#renderCss(sourceContent);
|
|
4106
|
+
}
|
|
4107
|
+
return this.#showcaseFromSources(sources);
|
|
4108
|
+
}
|
|
4109
|
+
async #showcaseFromSources(sources) {
|
|
4110
|
+
const issues = await this.#validator.validate(sources);
|
|
4111
|
+
if (this.#hasValidationErrors(issues)) throw new Error(this.#formatValidationIssues(issues));
|
|
4112
|
+
const cssString = await this.#converter.convert(sources);
|
|
4113
|
+
return this.#renderCss(cssString);
|
|
4114
|
+
}
|
|
4115
|
+
#renderCss(cssString) {
|
|
4116
|
+
return this.#renderer.renderPage(this.#parser.parse(cssString));
|
|
4117
|
+
}
|
|
4118
|
+
#hasValidationErrors(issues) {
|
|
4119
|
+
return issues.some((issue) => issue.severity === "error");
|
|
4120
|
+
}
|
|
4121
|
+
#formatValidationIssues(issues) {
|
|
4122
|
+
return issues.filter((issue) => issue.severity === "error").map((issue) => `[${issue.name}] ${issue.sourcePath} - ${issue.message}`).join("\n");
|
|
4123
|
+
}
|
|
4124
|
+
#isCssContent(content) {
|
|
4125
|
+
return /(^|\s)--[a-zA-Z0-9_-]+\s*:/.test(content) || /(^|\s):root\b/.test(content) || /(^|\s)@layer\b/.test(content);
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
//#endregion
|
|
4129
|
+
//#region src/core/validation/dtcg/DtcgSchemaValidator.ts
|
|
4130
|
+
var FORMAT_SCHEMA_ID = "https://www.designtokens.org/schemas/2025.10/format.json";
|
|
4131
|
+
/**
|
|
4132
|
+
* Validates DTCG JSON sources against the official DTCG JSON Schema.
|
|
4133
|
+
* Accepts DTCG JSON sources only.
|
|
4134
|
+
*/
|
|
4135
|
+
var DtcgSchemaValidator = class {
|
|
4136
|
+
name = "dtcg";
|
|
4137
|
+
async validate(sources) {
|
|
4138
|
+
const validator = (await this.#createAjv()).getSchema(FORMAT_SCHEMA_ID);
|
|
4139
|
+
if (!validator) throw new Error(`AJV schema "${FORMAT_SCHEMA_ID}" was not loaded.`);
|
|
4140
|
+
const issues = [];
|
|
4141
|
+
for (const source of sources) {
|
|
4142
|
+
const content = await new Source(source).getContent();
|
|
4143
|
+
if (validator(JSON.parse(content))) continue;
|
|
4144
|
+
const errors = validator.errors ?? [];
|
|
4145
|
+
for (const error of errors) issues.push(this.#toValidationIssue(source, error));
|
|
4146
|
+
}
|
|
4147
|
+
return issues;
|
|
4148
|
+
}
|
|
4149
|
+
async #createAjv() {
|
|
4150
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
4151
|
+
const schemaDir = path.resolve(moduleDir, "schemas/2025.10");
|
|
4152
|
+
const ajv = new Ajv({
|
|
4153
|
+
allErrors: true,
|
|
4154
|
+
strict: false
|
|
4155
|
+
});
|
|
4156
|
+
addFormats(ajv);
|
|
4157
|
+
for (const schemaFilePath of await listJsonFiles(schemaDir)) {
|
|
4158
|
+
const rawSchema = await readFile(schemaFilePath, "utf8");
|
|
4159
|
+
const schema = JSON.parse(rawSchema);
|
|
4160
|
+
ajv.addSchema(schema, schema.$id ?? schemaFilePath);
|
|
4161
|
+
}
|
|
4162
|
+
return ajv;
|
|
4163
|
+
}
|
|
4164
|
+
#toValidationIssue(sourcePath, error) {
|
|
4165
|
+
const instancePath = error.instancePath || "/";
|
|
4166
|
+
const message = error.message ?? "Validation error.";
|
|
4167
|
+
return {
|
|
4168
|
+
name: this.name,
|
|
4169
|
+
sourcePath,
|
|
4170
|
+
severity: "error",
|
|
4171
|
+
message: `${instancePath}: ${message}`,
|
|
4172
|
+
raw: error
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
4176
|
+
async function listJsonFiles(directoryPath) {
|
|
4177
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
4178
|
+
const files = [];
|
|
4179
|
+
for (const entry of entries) {
|
|
4180
|
+
const entryPath = path.join(directoryPath, entry.name);
|
|
4181
|
+
if (entry.isDirectory()) files.push(...await listJsonFiles(entryPath));
|
|
4182
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".json")) files.push(entryPath);
|
|
4183
|
+
}
|
|
4184
|
+
return files;
|
|
4185
|
+
}
|
|
4186
|
+
//#endregion
|
|
4187
|
+
//#region src/core/validation/hrdt/HrdtTokenValidator.ts
|
|
4188
|
+
var SCHEMA_ID = "https://designtokens.local/schemas/hrdt-tokens.json";
|
|
4189
|
+
/**
|
|
4190
|
+
* Validator based on AJV and JSON Schema.
|
|
4191
|
+
* Accepts HRDT sources only.
|
|
4192
|
+
*/
|
|
4193
|
+
var HrdtTokenValidator = class {
|
|
4194
|
+
name = "hrdt";
|
|
4195
|
+
async validate(sources) {
|
|
4196
|
+
const validator = (await this.#createAjv()).getSchema(SCHEMA_ID);
|
|
4197
|
+
if (!validator) throw new Error(`AJV schema "${SCHEMA_ID}" was not loaded.`);
|
|
4198
|
+
const issues = [];
|
|
4199
|
+
for (const source of sources) {
|
|
4200
|
+
const content = await new Source(source).getContent();
|
|
4201
|
+
if (validator(new HrdtTokenReader().parseRaw(content))) continue;
|
|
4202
|
+
const errors = validator.errors ?? [];
|
|
4203
|
+
for (const error of errors) issues.push(this.#toValidationIssue(source, error));
|
|
4204
|
+
}
|
|
4205
|
+
return issues;
|
|
4206
|
+
}
|
|
4207
|
+
async #createAjv() {
|
|
4208
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
4209
|
+
const schemaPath = path.resolve(moduleDir, "schemas/hrdt-tokens.json");
|
|
4210
|
+
const ajv = new Ajv({
|
|
4211
|
+
allErrors: true,
|
|
4212
|
+
strict: false
|
|
4213
|
+
});
|
|
4214
|
+
addFormats(ajv);
|
|
4215
|
+
const rawSchema = await readFile(schemaPath, "utf8");
|
|
4216
|
+
const schema = JSON.parse(rawSchema);
|
|
4217
|
+
ajv.addSchema(schema, schema.$id ?? schemaPath);
|
|
4218
|
+
return ajv;
|
|
4219
|
+
}
|
|
4220
|
+
#toValidationIssue(sourcePath, error) {
|
|
4221
|
+
const instancePath = error.instancePath || "/";
|
|
4222
|
+
const message = error.message ?? "Validation error.";
|
|
4223
|
+
return {
|
|
4224
|
+
name: this.name,
|
|
4225
|
+
sourcePath,
|
|
4226
|
+
severity: "error",
|
|
4227
|
+
message: `${instancePath}: ${message}`,
|
|
4228
|
+
raw: error
|
|
4229
|
+
};
|
|
4230
|
+
}
|
|
4231
|
+
};
|
|
4232
|
+
//#endregion
|
|
4233
|
+
//#region src/core/validation/DtcgTokenValidator.ts
|
|
4234
|
+
function extractThemeName(source) {
|
|
4235
|
+
const withoutExt = (source.split("/").at(-1)?.split("\\").at(-1) ?? source).replace(/\.(json|ya?ml)$/i, "");
|
|
4236
|
+
const dotIndex = withoutExt.lastIndexOf(".");
|
|
4237
|
+
return dotIndex > 0 ? withoutExt.slice(dotIndex + 1) : withoutExt;
|
|
4238
|
+
}
|
|
4239
|
+
async function readDoc(source) {
|
|
4240
|
+
const content = await new Source(source).getContent();
|
|
4241
|
+
return /\.(ya?ml)$/i.test(source) ? new HrdtTokenReader().parse(content) : new DtcgJsonReader().parse(content);
|
|
4242
|
+
}
|
|
4243
|
+
/**
|
|
4244
|
+
* Validates DTCG JSON or HRDT token files using the internal model.
|
|
4245
|
+
*
|
|
4246
|
+
* Accepts one base file plus optional theme files. Validates references,
|
|
4247
|
+
* cycles, type mismatches, and deprecated token usage.
|
|
4248
|
+
*/
|
|
4249
|
+
var DtcgTokenValidator = class {
|
|
4250
|
+
#name = "internal";
|
|
4251
|
+
async validate(sources) {
|
|
4252
|
+
const hrdtSources = sources.filter((s) => /\.(ya?ml)$/i.test(s));
|
|
4253
|
+
const hrdtIssues = hrdtSources.length > 0 ? await new HrdtTokenValidator().validate(hrdtSources) : [];
|
|
4254
|
+
if (hrdtIssues.some((i) => i.severity === "error")) return hrdtIssues;
|
|
4255
|
+
const jsonSources = sources.filter((s) => !/\.(ya?ml)$/i.test(s));
|
|
4256
|
+
const schemaIssues = jsonSources.length > 0 ? await new DtcgSchemaValidator().validate(jsonSources) : [];
|
|
4257
|
+
if (schemaIssues.some((i) => i.severity === "error")) return schemaIssues;
|
|
4258
|
+
const [base, ...rest] = await Promise.all(sources.map(readDoc));
|
|
4259
|
+
return new DtcgList(base, new Map(rest.map((doc, i) => [extractThemeName(sources[i + 1]), doc]))).validate().map((issue) => ({
|
|
4260
|
+
name: this.#name,
|
|
4261
|
+
sourcePath: sources.join(", "),
|
|
4262
|
+
message: `${issue.tokenPath}: ${issue.message}`,
|
|
4263
|
+
severity: issue.severity
|
|
4264
|
+
}));
|
|
4265
|
+
}
|
|
4266
|
+
};
|
|
4267
|
+
//#endregion
|
|
4268
|
+
//#region src/core/DesignToken.ts
|
|
4269
|
+
function extractFileName(name) {
|
|
4270
|
+
return name.split("/").at(-1)?.split("\\").at(-1) ?? name;
|
|
4271
|
+
}
|
|
4272
|
+
function extractTheme(fileName) {
|
|
4273
|
+
const withoutExt = fileName.replace(/\.json$/i, "");
|
|
4274
|
+
const dotIndex = withoutExt.lastIndexOf(".");
|
|
4275
|
+
return dotIndex > 0 ? withoutExt.slice(dotIndex + 1) : withoutExt;
|
|
4276
|
+
}
|
|
4277
|
+
/**
|
|
4278
|
+
* A single design token, identified by file name.
|
|
4279
|
+
*
|
|
4280
|
+
* @remarks
|
|
4281
|
+
* The theme is extracted from the file name:
|
|
4282
|
+
* - `tokens.dark.json` → theme `dark`
|
|
4283
|
+
* - `base.json` → theme `base`
|
|
4284
|
+
* The first token in the {@link DesignTokens} collection is marked as base.
|
|
4285
|
+
*/
|
|
4286
|
+
var DesignToken = class {
|
|
4287
|
+
#isBase;
|
|
4288
|
+
/** Theme name extracted from the file name. */
|
|
4289
|
+
themeName;
|
|
4290
|
+
/**
|
|
4291
|
+
* @param name - Path to the token file or its name.
|
|
4292
|
+
* @param isBase - Whether the token is base.
|
|
4293
|
+
*/
|
|
4294
|
+
constructor(name, isBase) {
|
|
4295
|
+
this.themeName = extractTheme(extractFileName(name));
|
|
4296
|
+
this.#isBase = isBase;
|
|
4297
|
+
}
|
|
4298
|
+
/** Whether the token is base. */
|
|
4299
|
+
get isBase() {
|
|
4300
|
+
return this.#isBase;
|
|
4301
|
+
}
|
|
4302
|
+
/**
|
|
4303
|
+
* Token theme name.
|
|
4304
|
+
* @deprecated Use {@link DesignToken.themeName}.
|
|
4305
|
+
*/
|
|
4306
|
+
get name() {
|
|
4307
|
+
return this.themeName;
|
|
4308
|
+
}
|
|
4309
|
+
/**
|
|
4310
|
+
* CSS selector for the token theme.
|
|
4311
|
+
* @returns `:root` for base theme, `:root[data-theme="<theme>"]` for others.
|
|
4312
|
+
*/
|
|
4313
|
+
get selector() {
|
|
4314
|
+
return this.#isBase ? ":root" : `:root[data-theme="${this.themeName}"]`;
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
/**
|
|
4318
|
+
* Ordered collection of design tokens.
|
|
4319
|
+
*
|
|
4320
|
+
* @remarks
|
|
4321
|
+
* The first token in the collection is always the base token.
|
|
4322
|
+
* Use {@link DesignTokens.fromNames} to create from a list of file names
|
|
4323
|
+
* or {@link DesignTokens.scan} to scan a directory.
|
|
4324
|
+
*/
|
|
4325
|
+
var DesignTokens = class DesignTokens {
|
|
4326
|
+
tokens;
|
|
4327
|
+
constructor(tokens) {
|
|
4328
|
+
this.tokens = tokens;
|
|
4329
|
+
}
|
|
4330
|
+
/**
|
|
4331
|
+
* Scans a directory and creates a collection from found token files.
|
|
4332
|
+
*
|
|
4333
|
+
* @param directory - Path to the directory to scan.
|
|
4334
|
+
* @returns Collection of tokens sorted by file name.
|
|
4335
|
+
* @throws Error if directory is empty.
|
|
4336
|
+
*/
|
|
4337
|
+
static async scan(directory) {
|
|
4338
|
+
const jsonFiles = (await readdir(directory)).sort().filter((file) => file.toLowerCase().endsWith(".json"));
|
|
4339
|
+
if (jsonFiles.length === 0) throw new Error(`No token files found in directory: ${directory}`);
|
|
4340
|
+
return new DesignTokens(jsonFiles.map((file, index) => new DesignToken(`${directory}/${file}`, index === 0)));
|
|
4341
|
+
}
|
|
4342
|
+
/**
|
|
4343
|
+
* Creates a token collection from an array of file names.
|
|
4344
|
+
*
|
|
4345
|
+
* @param names - Array of token file paths or names.
|
|
4346
|
+
* The first element is considered the base token.
|
|
4347
|
+
* @returns Collection of tokens in the order of given names.
|
|
4348
|
+
* @throws Error if the array is empty.
|
|
4349
|
+
*/
|
|
4350
|
+
static fromNames(names) {
|
|
4351
|
+
if (names.length === 0) throw new Error("No token sources provided");
|
|
4352
|
+
return new DesignTokens(names.map((name, index) => new DesignToken(name, index === 0)));
|
|
4353
|
+
}
|
|
4354
|
+
/** Whether the collection has a base token. */
|
|
4355
|
+
get hasBaseToken() {
|
|
4356
|
+
return this.tokens.length > 0;
|
|
4357
|
+
}
|
|
4358
|
+
/** The base token of the collection (first token). */
|
|
4359
|
+
get baseToken() {
|
|
4360
|
+
return this.tokens[0];
|
|
4361
|
+
}
|
|
4362
|
+
/**
|
|
4363
|
+
* Returns a token by theme name.
|
|
4364
|
+
*
|
|
4365
|
+
* @param themeName - Theme name (for example, `"dark"`, `"high-contrast"`).
|
|
4366
|
+
* @returns Token with the given theme.
|
|
4367
|
+
* @throws Error if the token with the given theme is not found.
|
|
4368
|
+
*/
|
|
4369
|
+
getToken(themeName) {
|
|
4370
|
+
const token = this.tokens.find((t) => t.name === themeName);
|
|
4371
|
+
if (!token) throw new Error(`Unknown token theme: ${themeName}`);
|
|
4372
|
+
return token;
|
|
4373
|
+
}
|
|
4374
|
+
/**
|
|
4375
|
+
* Returns a CSS selector for the given theme.
|
|
4376
|
+
*
|
|
4377
|
+
* @param themeName - Theme name.
|
|
4378
|
+
* @param isBase - Whether the theme is base (`:root`).
|
|
4379
|
+
* @returns CSS selector for the theme.
|
|
4380
|
+
* @throws Error if the theme is not base and is not found in the collection.
|
|
4381
|
+
*/
|
|
4382
|
+
getSelector(themeName, isBase) {
|
|
4383
|
+
return isBase ? ":root" : this.getToken(themeName).selector;
|
|
4384
|
+
}
|
|
4385
|
+
};
|
|
4386
|
+
//#endregion
|
|
4387
|
+
//#region src/core/io/DtcgJsonWriter.ts
|
|
4388
|
+
var DTCG_SCHEMA = "https://www.designtokens.org/schemas/2025.10/format.json";
|
|
4389
|
+
/**
|
|
4390
|
+
* Serializes a {@link Dtcg} document to a DTCG JSON string.
|
|
4391
|
+
*
|
|
4392
|
+
* @see https://tr.designtokens.org/format/
|
|
4393
|
+
*/
|
|
4394
|
+
var DtcgJsonWriter = class {
|
|
4395
|
+
write(doc) {
|
|
4396
|
+
return JSON.stringify(this.#toObject(doc), null, 2) + "\n";
|
|
4397
|
+
}
|
|
4398
|
+
#toObject(doc) {
|
|
4399
|
+
const result = { "$schema": DTCG_SCHEMA };
|
|
4400
|
+
for (const [key, child] of doc.entries()) result[key] = child instanceof TokenGroup ? this.#writeGroup(child) : this.#writeToken(child);
|
|
4401
|
+
return result;
|
|
4402
|
+
}
|
|
4403
|
+
#writeGroup(group) {
|
|
4404
|
+
const obj = {};
|
|
4405
|
+
if (group.type !== void 0) obj["$type"] = group.type;
|
|
4406
|
+
if (group.description !== void 0) obj["$description"] = group.description;
|
|
4407
|
+
if (group.deprecated !== void 0) obj["$deprecated"] = group.deprecated;
|
|
4408
|
+
if (group.extensions !== void 0) obj["$extensions"] = group.extensions;
|
|
4409
|
+
if (group.extends !== void 0) obj["$extends"] = group.extends instanceof TokenReference ? group.extends.toString() : group.extends;
|
|
4410
|
+
if (group.root !== void 0) obj["$root"] = this.#writeToken(group.root);
|
|
4411
|
+
for (const [key, child] of group.entries()) obj[key] = child instanceof TokenGroup ? this.#writeGroup(child) : this.#writeToken(child);
|
|
4412
|
+
return obj;
|
|
4413
|
+
}
|
|
4414
|
+
#writeToken(token) {
|
|
4415
|
+
const obj = {};
|
|
4416
|
+
if (token.type !== void 0) obj["$type"] = token.type;
|
|
4417
|
+
if (token.description !== void 0) obj["$description"] = token.description;
|
|
4418
|
+
if (token.deprecated !== void 0) obj["$deprecated"] = token.deprecated;
|
|
4419
|
+
if (token.extensions !== void 0) obj["$extensions"] = token.extensions;
|
|
4420
|
+
obj["$value"] = this.#writeValue(token);
|
|
4421
|
+
return obj;
|
|
4422
|
+
}
|
|
4423
|
+
#writeValue(token) {
|
|
4424
|
+
const value = token.value;
|
|
4425
|
+
if (value instanceof TokenReference) return value.toString();
|
|
4426
|
+
if (token instanceof ColorToken) return this.#writeColor(value);
|
|
4427
|
+
if (token instanceof DimensionToken) return this.#writeDimension(value);
|
|
4428
|
+
if (token instanceof FontFamilyToken) return value;
|
|
4429
|
+
if (token instanceof FontWeightToken) return value;
|
|
4430
|
+
if (token instanceof NumberToken) return value;
|
|
4431
|
+
if (token instanceof DurationToken) return this.#writeDuration(value);
|
|
4432
|
+
if (token instanceof CubicBezierToken) return this.#writeCubicBezier(value);
|
|
4433
|
+
if (token instanceof StrokeStyleToken) return this.#writeStrokeStyle(value);
|
|
4434
|
+
if (token instanceof BorderToken) return this.#writeBorder(value);
|
|
4435
|
+
if (token instanceof TransitionToken) return this.#writeTransition(value);
|
|
4436
|
+
if (token instanceof ShadowToken) return this.#writeShadow(value);
|
|
4437
|
+
if (token instanceof GradientToken) return this.#writeGradient(value);
|
|
4438
|
+
if (token instanceof TypographyToken) return this.#writeTypography(value);
|
|
4439
|
+
if (token instanceof AliasToken) return value.toString();
|
|
4440
|
+
return value;
|
|
4441
|
+
}
|
|
4442
|
+
#writeColor(color) {
|
|
4443
|
+
const obj = {
|
|
4444
|
+
colorSpace: color.colorSpace,
|
|
4445
|
+
components: color.components
|
|
4446
|
+
};
|
|
4447
|
+
if (color.alpha !== 1) obj["alpha"] = color.alpha;
|
|
4448
|
+
if (color.hex !== void 0) obj["hex"] = color.hex;
|
|
4449
|
+
return obj;
|
|
4450
|
+
}
|
|
4451
|
+
#writeDimension(dim) {
|
|
4452
|
+
return {
|
|
4453
|
+
value: dim.value,
|
|
4454
|
+
unit: dim.unit
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
4457
|
+
#writeDuration(dur) {
|
|
4458
|
+
return {
|
|
4459
|
+
value: dur.value,
|
|
4460
|
+
unit: dur.unit
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
#writeCubicBezier(cb) {
|
|
4464
|
+
return [
|
|
4465
|
+
cb.p1x,
|
|
4466
|
+
cb.p1y,
|
|
4467
|
+
cb.p2x,
|
|
4468
|
+
cb.p2y
|
|
4469
|
+
];
|
|
4470
|
+
}
|
|
4471
|
+
#writeStrokeStyle(value) {
|
|
4472
|
+
if (value instanceof StrokeStyleObject) return {
|
|
4473
|
+
dashArray: value.dashArray.map((d) => d instanceof TokenReference ? d.toString() : this.#writeDimension(d)),
|
|
4474
|
+
lineCap: value.lineCap
|
|
4475
|
+
};
|
|
4476
|
+
return value;
|
|
4477
|
+
}
|
|
4478
|
+
#writeBorder(border) {
|
|
4479
|
+
return {
|
|
4480
|
+
color: this.#writeColorOrRef(border.color),
|
|
4481
|
+
width: this.#writeDimensionOrRef(border.width),
|
|
4482
|
+
style: this.#writeStrokeStyleOrRef(border.style)
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
#writeTransition(transition) {
|
|
4486
|
+
return {
|
|
4487
|
+
duration: this.#writeDurationOrRef(transition.duration),
|
|
4488
|
+
delay: this.#writeDurationOrRef(transition.delay),
|
|
4489
|
+
timingFunction: this.#writeCubicBezierOrRef(transition.timingFunction)
|
|
4490
|
+
};
|
|
4491
|
+
}
|
|
4492
|
+
#writeShadow(value) {
|
|
4493
|
+
if (Array.isArray(value)) return value.map((item) => item instanceof TokenReference ? item.toString() : this.#writeShadowLayer(item));
|
|
4494
|
+
return this.#writeShadowLayer(value);
|
|
4495
|
+
}
|
|
4496
|
+
#writeShadowLayer(layer) {
|
|
4497
|
+
const obj = {
|
|
4498
|
+
color: this.#writeColorOrRef(layer.color),
|
|
4499
|
+
offsetX: this.#writeDimensionOrRef(layer.offsetX),
|
|
4500
|
+
offsetY: this.#writeDimensionOrRef(layer.offsetY),
|
|
4501
|
+
blur: this.#writeDimensionOrRef(layer.blur),
|
|
4502
|
+
spread: this.#writeDimensionOrRef(layer.spread)
|
|
4503
|
+
};
|
|
4504
|
+
if (layer.inset) obj["inset"] = layer.inset;
|
|
4505
|
+
return obj;
|
|
4506
|
+
}
|
|
4507
|
+
#writeGradient(stops) {
|
|
4508
|
+
return stops.map((stop) => stop instanceof TokenReference ? stop.toString() : this.#writeGradientStop(stop));
|
|
4509
|
+
}
|
|
4510
|
+
#writeGradientStop(stop) {
|
|
4511
|
+
return {
|
|
4512
|
+
color: this.#writeColorOrRef(stop.color),
|
|
4513
|
+
position: this.#writeNumberOrRef(stop.position)
|
|
4514
|
+
};
|
|
4515
|
+
}
|
|
4516
|
+
#writeTypography(typo) {
|
|
4517
|
+
const obj = {};
|
|
4518
|
+
if (typo.fontFamily !== void 0) obj["fontFamily"] = this.#writeFontFamilyOrRef(typo.fontFamily);
|
|
4519
|
+
if (typo.fontSize !== void 0) obj["fontSize"] = this.#writeDimensionOrRef(typo.fontSize);
|
|
4520
|
+
if (typo.fontWeight !== void 0) obj["fontWeight"] = this.#writeFontWeightOrRef(typo.fontWeight);
|
|
4521
|
+
if (typo.letterSpacing !== void 0) obj["letterSpacing"] = this.#writeDimensionOrRef(typo.letterSpacing);
|
|
4522
|
+
if (typo.lineHeight !== void 0) obj["lineHeight"] = this.#writeNumberOrRef(typo.lineHeight);
|
|
4523
|
+
return obj;
|
|
4524
|
+
}
|
|
4525
|
+
#writeColorOrRef(value) {
|
|
4526
|
+
return value instanceof TokenReference ? value.toString() : this.#writeColor(value);
|
|
4527
|
+
}
|
|
4528
|
+
#writeDimensionOrRef(value) {
|
|
4529
|
+
return value instanceof TokenReference ? value.toString() : this.#writeDimension(value);
|
|
4530
|
+
}
|
|
4531
|
+
#writeDurationOrRef(value) {
|
|
4532
|
+
return value instanceof TokenReference ? value.toString() : this.#writeDuration(value);
|
|
4533
|
+
}
|
|
4534
|
+
#writeCubicBezierOrRef(value) {
|
|
4535
|
+
return value instanceof TokenReference ? value.toString() : this.#writeCubicBezier(value);
|
|
4536
|
+
}
|
|
4537
|
+
#writeStrokeStyleOrRef(value) {
|
|
4538
|
+
if (value instanceof TokenReference) return value.toString();
|
|
4539
|
+
return this.#writeStrokeStyle(value);
|
|
4540
|
+
}
|
|
4541
|
+
#writeFontFamilyOrRef(value) {
|
|
4542
|
+
return value instanceof TokenReference ? value.toString() : value;
|
|
4543
|
+
}
|
|
4544
|
+
#writeFontWeightOrRef(value) {
|
|
4545
|
+
return value instanceof TokenReference ? value.toString() : value;
|
|
4546
|
+
}
|
|
4547
|
+
#writeNumberOrRef(value) {
|
|
4548
|
+
return value instanceof TokenReference ? value.toString() : value;
|
|
4549
|
+
}
|
|
4550
|
+
};
|
|
4551
|
+
//#endregion
|
|
4552
|
+
//#region src/core/io/HrdtTokenWriter.ts
|
|
4553
|
+
/**
|
|
4554
|
+
* Serializes a {@link Dtcg} document to the HRDT token format.
|
|
4555
|
+
*
|
|
4556
|
+
* The HRDT format uses YAML syntax and mirrors what {@link HrdtTokenReader} accepts:
|
|
4557
|
+
* primitive tokens are grouped by type under `primitive.<type>`,
|
|
4558
|
+
* all other groups contain only alias references.
|
|
4559
|
+
*/
|
|
4560
|
+
var HrdtTokenWriter = class {
|
|
4561
|
+
write(doc) {
|
|
4562
|
+
const lines = [];
|
|
4563
|
+
for (const [key, child] of doc.entries()) if (child instanceof TokenGroup) {
|
|
4564
|
+
lines.push(`${key}:`);
|
|
4565
|
+
if (key === "primitive") this.#writePrimitiveRoot(child, lines, 1);
|
|
4566
|
+
else this.#writeReferenceGroup(child, lines, 1);
|
|
4567
|
+
lines.push("");
|
|
4568
|
+
}
|
|
4569
|
+
return lines.join("\n").trimEnd() + "\n";
|
|
4570
|
+
}
|
|
4571
|
+
#writePrimitiveRoot(group, lines, depth) {
|
|
4572
|
+
for (const [typeName, child] of group.entries()) {
|
|
4573
|
+
if (!(child instanceof TokenGroup)) continue;
|
|
4574
|
+
lines.push(`${this.#indent(depth)}${typeName}:`);
|
|
4575
|
+
for (const [name, token] of child.entries()) {
|
|
4576
|
+
if (token instanceof TokenGroup) continue;
|
|
4577
|
+
const serialized = this.#serializePrimitiveToken(token);
|
|
4578
|
+
if (typeof serialized === "string" && !serialized.includes("\n")) lines.push(`${this.#indent(depth + 1)}${name}: ${serialized}`);
|
|
4579
|
+
else if (typeof serialized === "string") {
|
|
4580
|
+
lines.push(`${this.#indent(depth + 1)}${name}:`);
|
|
4581
|
+
for (const subLine of serialized.split("\n")) lines.push(`${this.#indent(depth + 2)}${subLine}`);
|
|
4582
|
+
} else {
|
|
4583
|
+
lines.push(`${this.#indent(depth + 1)}${name}:`);
|
|
4584
|
+
for (const subLine of serialized) lines.push(`${this.#indent(depth + 2)}${subLine}`);
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
#writeReferenceGroup(group, lines, depth) {
|
|
4590
|
+
for (const [key, child] of group.entries()) if (child instanceof TokenGroup) {
|
|
4591
|
+
lines.push(`${this.#indent(depth)}${key}:`);
|
|
4592
|
+
this.#writeReferenceGroup(child, lines, depth + 1);
|
|
4593
|
+
} else {
|
|
4594
|
+
const value = child.value;
|
|
4595
|
+
const ref = value instanceof TokenReference ? value.toString() : String(value);
|
|
4596
|
+
lines.push(`${this.#indent(depth)}${key}: "${ref}"`);
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
#serializePrimitiveToken(token) {
|
|
4600
|
+
const value = token.value;
|
|
4601
|
+
if (value instanceof TokenReference) return `"${value}"`;
|
|
4602
|
+
if (token instanceof ColorToken) return this.#serializeColor(value);
|
|
4603
|
+
if (token instanceof DimensionToken) return this.#serializeDimension(value);
|
|
4604
|
+
if (token instanceof DurationToken) return this.#serializeDuration(value);
|
|
4605
|
+
if (token instanceof FontFamilyToken) return this.#serializeFontFamily(value);
|
|
4606
|
+
if (token instanceof FontWeightToken) return String(value);
|
|
4607
|
+
if (token instanceof NumberToken) return String(value);
|
|
4608
|
+
if (token instanceof CubicBezierToken) return this.#serializeCubicBezier(value);
|
|
4609
|
+
if (token instanceof StrokeStyleToken) return this.#serializeStrokeStyle(value);
|
|
4610
|
+
if (token instanceof BorderToken) return this.#serializeBorder(value);
|
|
4611
|
+
if (token instanceof TransitionToken) return this.#serializeTransition(value);
|
|
4612
|
+
if (token instanceof ShadowToken) return this.#serializeShadow(value);
|
|
4613
|
+
if (token instanceof GradientToken) return this.#serializeGradient(value);
|
|
4614
|
+
if (token instanceof TypographyToken) return this.#serializeTypography(value);
|
|
4615
|
+
if (token instanceof AliasToken) return `"${value}"`;
|
|
4616
|
+
return String(value);
|
|
4617
|
+
}
|
|
4618
|
+
#serializeColor(color) {
|
|
4619
|
+
if (color.hex) {
|
|
4620
|
+
if (color.alpha === 1) return color.hex;
|
|
4621
|
+
const alphaHex = Math.round(color.alpha * 255).toString(16).padStart(2, "0");
|
|
4622
|
+
return `${color.hex}${alphaHex}`;
|
|
4623
|
+
}
|
|
4624
|
+
const [r, g, b] = color.components;
|
|
4625
|
+
const toHex = (v) => typeof v === "number" ? Math.round(v * 255).toString(16).padStart(2, "0") : "00";
|
|
4626
|
+
const base = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
4627
|
+
if (color.alpha === 1) return base;
|
|
4628
|
+
return `${base}${Math.round(color.alpha * 255).toString(16).padStart(2, "0")}`;
|
|
4629
|
+
}
|
|
4630
|
+
#serializeDimension(dim) {
|
|
4631
|
+
return `${dim.value}${dim.unit}`;
|
|
4632
|
+
}
|
|
4633
|
+
#serializeDuration(dur) {
|
|
4634
|
+
return `${dur.value}${dur.unit}`;
|
|
4635
|
+
}
|
|
4636
|
+
#serializeFontFamily(value) {
|
|
4637
|
+
if (Array.isArray(value)) return `[${value.map((v) => v.includes(" ") ? `"${v}"` : v).join(", ")}]`;
|
|
4638
|
+
return value;
|
|
4639
|
+
}
|
|
4640
|
+
#serializeCubicBezier(cb) {
|
|
4641
|
+
return `[${cb.p1x}, ${cb.p1y}, ${cb.p2x}, ${cb.p2y}]`;
|
|
4642
|
+
}
|
|
4643
|
+
#serializeStrokeStyle(value) {
|
|
4644
|
+
if (typeof value === "string") return value;
|
|
4645
|
+
if (value instanceof StrokeStyleObject) return [`dashArray: [${value.dashArray.map((d) => d instanceof TokenReference ? `"${d}"` : this.#serializeDimension(d)).join(", ")}]`, `lineCap: ${value.lineCap}`];
|
|
4646
|
+
return String(value);
|
|
4647
|
+
}
|
|
4648
|
+
#serializeBorder(border) {
|
|
4649
|
+
return [
|
|
4650
|
+
`color: "${this.#serializeColorOrRef(border.color)}"`,
|
|
4651
|
+
`width: ${this.#serializeDimensionOrRef(border.width)}`,
|
|
4652
|
+
`style: ${this.#serializeStrokeStyleInline(border.style)}`
|
|
4653
|
+
];
|
|
4654
|
+
}
|
|
4655
|
+
#serializeTransition(t) {
|
|
4656
|
+
return [
|
|
4657
|
+
`duration: ${this.#serializeDurationOrRef(t.duration)}`,
|
|
4658
|
+
`delay: ${this.#serializeDurationOrRef(t.delay)}`,
|
|
4659
|
+
`timingFunction: ${this.#serializeTimingFunctionOrRef(t.timingFunction)}`
|
|
4660
|
+
];
|
|
4661
|
+
}
|
|
4662
|
+
#serializeShadow(value) {
|
|
4663
|
+
const layers = Array.isArray(value) ? value : [value];
|
|
4664
|
+
if (layers.length === 1) return this.#serializeShadowLayerLines(layers[0]);
|
|
4665
|
+
const result = [];
|
|
4666
|
+
for (const layer of layers) if (layer instanceof TokenReference) result.push(`- "${layer}"`);
|
|
4667
|
+
else {
|
|
4668
|
+
const [first, ...rest] = this.#serializeShadowLayerLines(layer);
|
|
4669
|
+
result.push(`- ${first}`);
|
|
4670
|
+
result.push(...rest.map((l) => ` ${l}`));
|
|
4671
|
+
}
|
|
4672
|
+
return result;
|
|
4673
|
+
}
|
|
4674
|
+
#serializeShadowLayerLines(layer) {
|
|
4675
|
+
const lines = [
|
|
4676
|
+
`color: "${this.#serializeColorOrRef(layer.color)}"`,
|
|
4677
|
+
`offsetX: ${this.#serializeDimensionOrRef(layer.offsetX)}`,
|
|
4678
|
+
`offsetY: ${this.#serializeDimensionOrRef(layer.offsetY)}`,
|
|
4679
|
+
`blur: ${this.#serializeDimensionOrRef(layer.blur)}`,
|
|
4680
|
+
`spread: ${this.#serializeDimensionOrRef(layer.spread)}`
|
|
4681
|
+
];
|
|
4682
|
+
if (layer.inset) lines.push("inset: true");
|
|
4683
|
+
return lines;
|
|
4684
|
+
}
|
|
4685
|
+
#serializeGradient(stops) {
|
|
4686
|
+
const result = [];
|
|
4687
|
+
for (const stop of stops) if (stop instanceof TokenReference) result.push(`- "${stop}"`);
|
|
4688
|
+
else {
|
|
4689
|
+
result.push(`- color: "${this.#serializeColorOrRef(stop.color)}"`);
|
|
4690
|
+
result.push(` position: ${stop.position instanceof TokenReference ? `"${stop.position}"` : stop.position}`);
|
|
4691
|
+
}
|
|
4692
|
+
return result;
|
|
4693
|
+
}
|
|
4694
|
+
#serializeTypography(typo) {
|
|
4695
|
+
const lines = [];
|
|
4696
|
+
if (typo.fontFamily !== void 0) {
|
|
4697
|
+
const ff = typo.fontFamily instanceof TokenReference ? `"${typo.fontFamily}"` : this.#serializeFontFamily(typo.fontFamily);
|
|
4698
|
+
lines.push(`fontFamily: ${ff}`);
|
|
4699
|
+
}
|
|
4700
|
+
if (typo.fontSize !== void 0) lines.push(`fontSize: ${this.#serializeDimensionOrRef(typo.fontSize)}`);
|
|
4701
|
+
if (typo.fontWeight !== void 0) lines.push(`fontWeight: ${typo.fontWeight instanceof TokenReference ? `"${typo.fontWeight}"` : typo.fontWeight}`);
|
|
4702
|
+
if (typo.letterSpacing !== void 0) lines.push(`letterSpacing: ${this.#serializeDimensionOrRef(typo.letterSpacing)}`);
|
|
4703
|
+
if (typo.lineHeight !== void 0) lines.push(`lineHeight: ${typo.lineHeight instanceof TokenReference ? `"${typo.lineHeight}"` : typo.lineHeight}`);
|
|
4704
|
+
return lines;
|
|
4705
|
+
}
|
|
4706
|
+
#serializeColorOrRef(value) {
|
|
4707
|
+
return value instanceof TokenReference ? value.toString() : this.#serializeColor(value);
|
|
4708
|
+
}
|
|
4709
|
+
#serializeDimensionOrRef(value) {
|
|
4710
|
+
return value instanceof TokenReference ? `"${value}"` : this.#serializeDimension(value);
|
|
4711
|
+
}
|
|
4712
|
+
#serializeDurationOrRef(value) {
|
|
4713
|
+
return value instanceof TokenReference ? `"${value}"` : this.#serializeDuration(value);
|
|
4714
|
+
}
|
|
4715
|
+
#serializeTimingFunctionOrRef(value) {
|
|
4716
|
+
return value instanceof TokenReference ? `"${value}"` : this.#serializeCubicBezier(value);
|
|
4717
|
+
}
|
|
4718
|
+
#serializeStrokeStyleInline(value) {
|
|
4719
|
+
if (typeof value === "string") return value;
|
|
4720
|
+
if (value instanceof TokenReference) return `"${value}"`;
|
|
4721
|
+
if (value instanceof StrokeStyleObject) return `{dashArray: [${value.dashArray.map((d) => d instanceof TokenReference ? `"${d}"` : this.#serializeDimension(d)).join(", ")}], lineCap: ${value.lineCap}}`;
|
|
4722
|
+
return String(value);
|
|
4723
|
+
}
|
|
4724
|
+
#indent(depth) {
|
|
4725
|
+
return " ".repeat(depth);
|
|
4726
|
+
}
|
|
4727
|
+
};
|
|
4728
|
+
//#endregion
|
|
4729
|
+
//#region src/index.ts
|
|
4730
|
+
function createTokenCssConverter() {
|
|
4731
|
+
return new DtcgTokenCssConverter();
|
|
4732
|
+
}
|
|
4733
|
+
function createTokenHtmlShowcase() {
|
|
4734
|
+
return new TokenHtmlShowcaseBuilder(new DtcgTokenValidator(), new DtcgTokenCssConverter());
|
|
4735
|
+
}
|
|
4736
|
+
//#endregion
|
|
4737
|
+
export { DesignToken, DesignTokens, Dtcg, DtcgJsonReader, DtcgJsonReaderError, DtcgJsonWriter, DtcgList, DtcgSchemaValidator, DtcgTokenCssConverter, DtcgTokenValidator, HrdtTokenReader, HrdtTokenReaderError, HrdtTokenValidator, HrdtTokenWriter, Source, TokenGroup, TokenHtmlShowcaseBuilder, TokenNode, TokenReference, createTokenCssConverter, createTokenHtmlShowcase };
|