material-theme-builder 2.1.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4,19 +4,16 @@
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
6
  import { Command, Option } from "commander";
7
+ import { z } from "zod";
7
8
 
8
9
  // src/lib/builder.ts
9
10
  import {
10
- argbFromHex,
11
+ argbFromHex as argbFromHex2,
11
12
  Blend,
12
- blueFromArgb,
13
13
  DynamicColor,
14
- DynamicScheme,
15
- greenFromArgb,
16
- Hct,
17
- hexFromArgb,
18
- MaterialDynamicColors,
19
- redFromArgb,
14
+ DynamicScheme as DynamicScheme2,
15
+ Hct as Hct2,
16
+ MaterialDynamicColors as MaterialDynamicColors2,
20
17
  SchemeContent,
21
18
  SchemeExpressive,
22
19
  SchemeFidelity,
@@ -24,9 +21,682 @@ import {
24
21
  SchemeNeutral,
25
22
  SchemeTonalSpot,
26
23
  SchemeVibrant,
24
+ TonalPalette as TonalPalette2
25
+ } from "@material/material-color-utilities";
26
+ import { kebabCase as kebabCase4, upperFirst as upperFirst2 } from "lodash-es";
27
+
28
+ // src/lib/builder.css.ts
29
+ import {
30
+ hexFromArgb
31
+ } from "@material/material-color-utilities";
32
+ import { kebabCase } from "lodash-es";
33
+ function buildCss(ctx) {
34
+ const {
35
+ prefix,
36
+ allPalettes,
37
+ mergedColorsLight,
38
+ mergedColorsDark,
39
+ tokenToPalette,
40
+ allPaletteNamesKebab
41
+ } = ctx;
42
+ function buildRefPaletteLookup() {
43
+ const lookup = {};
44
+ for (const [name, palette] of Object.entries(allPalettes)) {
45
+ const paletteName = kebabCase(name);
46
+ for (const tone of STANDARD_TONES) {
47
+ const hex = hexFromArgb(palette.tone(tone));
48
+ if (!lookup[hex]) lookup[hex] = [];
49
+ lookup[hex].push({ paletteName, tone });
50
+ }
51
+ }
52
+ return lookup;
53
+ }
54
+ function sysColorVar(colorName, colorValue, lookup) {
55
+ const name = `--${prefix}-sys-color-${kebabCase(colorName)}`;
56
+ const hex = hexFromArgb(colorValue);
57
+ const matches = lookup[hex];
58
+ if (matches && matches.length > 0) {
59
+ const preferred = tokenToPalette[colorName] ?? deriveCustomPaletteName(colorName, allPaletteNamesKebab);
60
+ const match = (preferred ? matches.find((m) => m.paletteName === preferred) : void 0) ?? matches[0];
61
+ if (match) {
62
+ return `${name}:var(--${prefix}-ref-palette-${match.paletteName}-${match.tone});`;
63
+ }
64
+ }
65
+ return `${name}:${hex};`;
66
+ }
67
+ function toCssVars(mergedColors, lookup) {
68
+ return Object.entries(mergedColors).map(([name, value]) => sysColorVar(name, value, lookup)).join(" ");
69
+ }
70
+ function refPaletteVar(paletteName, tone, colorValue) {
71
+ const name = `--${prefix}-ref-palette-${paletteName}-${tone}`;
72
+ const value = hexFromArgb(colorValue);
73
+ return `${name}:${value};`;
74
+ }
75
+ function generateTonalPaletteVars(paletteName, palette) {
76
+ return STANDARD_TONES.map((tone) => {
77
+ const color = palette.tone(tone);
78
+ return refPaletteVar(paletteName, tone, color);
79
+ }).join(" ");
80
+ }
81
+ function generateTonalVars() {
82
+ return Object.entries(allPalettes).map(
83
+ ([name, palette]) => generateTonalPaletteVars(kebabCase(name), palette)
84
+ ).join(" ");
85
+ }
86
+ const refPaletteLookup = buildRefPaletteLookup();
87
+ const lightVars = toCssVars(mergedColorsLight, refPaletteLookup);
88
+ const darkVars = toCssVars(mergedColorsDark, refPaletteLookup);
89
+ const tonalVars = generateTonalVars();
90
+ return `
91
+ :root { ${lightVars} ${tonalVars} }
92
+ .dark { ${darkVars} ${tonalVars} }
93
+ `;
94
+ }
95
+
96
+ // src/lib/builder.figma.ts
97
+ import {
98
+ blueFromArgb,
99
+ greenFromArgb,
100
+ hexFromArgb as hexFromArgb2,
101
+ redFromArgb
102
+ } from "@material/material-color-utilities";
103
+ import { kebabCase as kebabCase2, startCase } from "lodash-es";
104
+ function buildFigmaVariables(ctx) {
105
+ const {
106
+ allPalettes,
107
+ mergedColorsLight,
108
+ mergedColorsDark,
109
+ tokenToPalette,
110
+ allPaletteNamesKebab
111
+ } = ctx;
112
+ const variables = [];
113
+ const paletteHexMap = {};
114
+ for (const [name, palette] of Object.entries(allPalettes)) {
115
+ const paletteName = startCase(name);
116
+ paletteHexMap[paletteName] = {};
117
+ for (const tone of STANDARD_TONES) {
118
+ const argb = palette.tone(tone);
119
+ const path2 = `ref/palette/${paletteName}/${tone}`;
120
+ const hex = hexFromArgb2(argb).toUpperCase();
121
+ paletteHexMap[paletteName][hex] = path2;
122
+ const color = {
123
+ r: redFromArgb(argb) / 255,
124
+ g: greenFromArgb(argb) / 255,
125
+ b: blueFromArgb(argb) / 255,
126
+ a: 1
127
+ };
128
+ variables.push({
129
+ path: path2,
130
+ scopes: ["ALL_SCOPES"],
131
+ values: { Light: color, Dark: color }
132
+ });
133
+ }
134
+ }
135
+ function findAlias(hex, tokenName) {
136
+ const preferredKebab = tokenToPalette[tokenName] ?? deriveCustomPaletteName(tokenName, allPaletteNamesKebab);
137
+ const preferred = preferredKebab ? startCase(preferredKebab) : void 0;
138
+ if (preferred && paletteHexMap[preferred]?.[hex]) {
139
+ return paletteHexMap[preferred][hex];
140
+ }
141
+ for (const palHexes of Object.values(paletteHexMap)) {
142
+ if (palHexes[hex]) return palHexes[hex];
143
+ }
144
+ return null;
145
+ }
146
+ function resolveValue(argb, tokenName) {
147
+ const hex = hexFromArgb2(argb).toUpperCase();
148
+ const aliasPath = findAlias(hex, tokenName);
149
+ if (aliasPath) return { alias: aliasPath };
150
+ return {
151
+ r: redFromArgb(argb) / 255,
152
+ g: greenFromArgb(argb) / 255,
153
+ b: blueFromArgb(argb) / 255,
154
+ a: 1
155
+ };
156
+ }
157
+ for (const [name, lightArgb] of Object.entries(mergedColorsLight)) {
158
+ const darkArgb = mergedColorsDark[name] ?? lightArgb;
159
+ const description = isTokenName(name) ? tokenDescriptions[name] : void 0;
160
+ variables.push({
161
+ path: `sys/color/${startCase(name)}`,
162
+ ...description ? { description } : {},
163
+ scopes: ["ALL_SCOPES"],
164
+ values: {
165
+ Light: resolveValue(lightArgb, name),
166
+ Dark: resolveValue(darkArgb, name)
167
+ }
168
+ });
169
+ }
170
+ return variables;
171
+ }
172
+ function buildFigmaTokens(ctx) {
173
+ const { prefix } = ctx;
174
+ const variables = buildFigmaVariables(ctx);
175
+ function rgbToHex(r, g, b) {
176
+ const toHex = (c) => Math.round(c * 255).toString(16).padStart(2, "0");
177
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
178
+ }
179
+ function toDtcgValue(modeValue) {
180
+ if ("alias" in modeValue) {
181
+ return `{${modeValue.alias.replaceAll("/", ".")}}`;
182
+ }
183
+ return {
184
+ colorSpace: "srgb",
185
+ components: [modeValue.r, modeValue.g, modeValue.b],
186
+ alpha: modeValue.a,
187
+ hex: rgbToHex(modeValue.r, modeValue.g, modeValue.b)
188
+ };
189
+ }
190
+ function toDtcgExtensions(v) {
191
+ if (v.path.startsWith("ref/palette/")) {
192
+ return {
193
+ "com.figma.scopes": v.scopes ?? ["ALL_SCOPES"],
194
+ "com.figma.isOverride": true
195
+ };
196
+ }
197
+ const lastSegment = v.path.split("/").at(-1);
198
+ if (!lastSegment) return { "com.figma.scopes": v.scopes ?? ["ALL_SCOPES"] };
199
+ const tokenName = kebabCase2(lastSegment);
200
+ return {
201
+ "com.figma.scopes": v.scopes ?? ["ALL_SCOPES"],
202
+ "css.variable": `--${prefix}-sys-color-${tokenName}`
203
+ };
204
+ }
205
+ function buildToken(v, modeValue) {
206
+ return {
207
+ $type: "color",
208
+ $value: toDtcgValue(modeValue),
209
+ ...v.description ? { $description: v.description } : {},
210
+ $extensions: toDtcgExtensions(v)
211
+ };
212
+ }
213
+ function parsePath(path2) {
214
+ const parts = path2.split("/");
215
+ if (parts[0] === "ref" && parts[1] === "palette" && parts[2] && parts[3]) {
216
+ return {
217
+ kind: "palette",
218
+ paletteName: parts[2],
219
+ tone: parts[3]
220
+ };
221
+ }
222
+ if (parts[0] === "sys" && parts[1] === "color" && parts[2]) {
223
+ return { kind: "color", tokenName: parts[2] };
224
+ }
225
+ return null;
226
+ }
227
+ function buildModeFile(modeName) {
228
+ const palette = {};
229
+ const color = {};
230
+ for (const v of variables) {
231
+ const modeValue = v.values[modeName];
232
+ if (!modeValue) continue;
233
+ const parsed = parsePath(v.path);
234
+ if (!parsed) continue;
235
+ if (parsed.kind === "palette") {
236
+ const group = palette[parsed.paletteName] ??= {};
237
+ group[parsed.tone] = buildToken(v, modeValue);
238
+ } else {
239
+ color[parsed.tokenName] = buildToken(v, modeValue);
240
+ }
241
+ }
242
+ return {
243
+ $extensions: { "com.figma.modeName": modeName },
244
+ ref: { palette },
245
+ sys: { color }
246
+ };
247
+ }
248
+ return {
249
+ "Light.tokens.json": buildModeFile("Light"),
250
+ "Dark.tokens.json": buildModeFile("Dark")
251
+ };
252
+ }
253
+
254
+ // src/lib/builder.flutter.ts
255
+ import { hexFromArgb as hexFromArgb3 } from "@material/material-color-utilities";
256
+ import { upperFirst } from "lodash-es";
257
+ var FLUTTER_PROPERTIES = [
258
+ "primary",
259
+ "surfaceTint",
260
+ "onPrimary",
261
+ "primaryContainer",
262
+ "onPrimaryContainer",
263
+ "secondary",
264
+ "onSecondary",
265
+ "secondaryContainer",
266
+ "onSecondaryContainer",
267
+ "tertiary",
268
+ "onTertiary",
269
+ "tertiaryContainer",
270
+ "onTertiaryContainer",
271
+ "error",
272
+ "onError",
273
+ "errorContainer",
274
+ "onErrorContainer",
275
+ "background",
276
+ "onBackground",
277
+ "surface",
278
+ "onSurface",
279
+ "surfaceVariant",
280
+ "onSurfaceVariant",
281
+ "outline",
282
+ "outlineVariant",
283
+ "shadow",
284
+ "scrim",
285
+ "inverseSurface",
286
+ "inverseOnSurface",
287
+ "inversePrimary",
288
+ "primaryFixed",
289
+ "onPrimaryFixed",
290
+ "primaryFixedDim",
291
+ "onPrimaryFixedVariant",
292
+ "secondaryFixed",
293
+ "onSecondaryFixed",
294
+ "secondaryFixedDim",
295
+ "onSecondaryFixedVariant",
296
+ "tertiaryFixed",
297
+ "onTertiaryFixed",
298
+ "tertiaryFixedDim",
299
+ "onTertiaryFixedVariant",
300
+ "surfaceDim",
301
+ "surfaceBright",
302
+ "surfaceContainerLowest",
303
+ "surfaceContainerLow",
304
+ "surfaceContainer",
305
+ "surfaceContainerHigh",
306
+ "surfaceContainerHighest"
307
+ ];
308
+ var TOKEN_TO_FLUTTER = {
309
+ inverseOnSurface: "onInverseSurface"
310
+ };
311
+ function toFlutterColor(argb) {
312
+ const hex = hexFromArgb3(argb).replace("#", "").toUpperCase();
313
+ return `Color(0xFF${hex})`;
314
+ }
315
+ function flutterPropName(m3Token) {
316
+ return TOKEN_TO_FLUTTER[m3Token] ?? m3Token;
317
+ }
318
+ function buildFlutter(ctx) {
319
+ const { mergedColorsLight, mergedColorsDark, hexCustomColors } = ctx;
320
+ function buildColorScheme(name, brightness, colors) {
321
+ const lines = [];
322
+ lines.push(`const ${name} = ColorScheme(`);
323
+ lines.push(` brightness: Brightness.${brightness},`);
324
+ for (const token of FLUTTER_PROPERTIES) {
325
+ const argb = colors[token];
326
+ if (argb === void 0) continue;
327
+ lines.push(` ${flutterPropName(token)}: ${toFlutterColor(argb)},`);
328
+ }
329
+ lines.push(");");
330
+ return lines.join("\n");
331
+ }
332
+ function buildCustomColors() {
333
+ if (hexCustomColors.length === 0) return "";
334
+ const sections = [];
335
+ for (const cc of hexCustomColors) {
336
+ const ccName = cc.name;
337
+ const tokens = [
338
+ ccName,
339
+ `on${upperFirst(ccName)}`,
340
+ `${ccName}Container`,
341
+ `on${upperFirst(ccName)}Container`
342
+ ];
343
+ const lightLines = [];
344
+ const darkLines = [];
345
+ for (const token of tokens) {
346
+ const lightArgb = mergedColorsLight[token];
347
+ const darkArgb = mergedColorsDark[token];
348
+ if (lightArgb !== void 0) {
349
+ lightLines.push(
350
+ `const light${upperFirst(token)} = ${toFlutterColor(lightArgb)};`
351
+ );
352
+ }
353
+ if (darkArgb !== void 0) {
354
+ darkLines.push(
355
+ `const dark${upperFirst(token)} = ${toFlutterColor(darkArgb)};`
356
+ );
357
+ }
358
+ }
359
+ sections.push([...lightLines, ...darkLines].join("\n"));
360
+ }
361
+ return "\n\n" + sections.join("\n\n");
362
+ }
363
+ const header = "import 'package:flutter/material.dart';";
364
+ const light = buildColorScheme(
365
+ "lightColorScheme",
366
+ "light",
367
+ mergedColorsLight
368
+ );
369
+ const dark = buildColorScheme("darkColorScheme", "dark", mergedColorsDark);
370
+ const custom = buildCustomColors();
371
+ return `${header}
372
+
373
+ ${light}
374
+
375
+ ${dark}${custom}
376
+ `;
377
+ }
378
+
379
+ // src/lib/builder.json.ts
380
+ import {
381
+ argbFromHex,
382
+ DynamicScheme,
383
+ Hct,
384
+ hexFromArgb as hexFromArgb4,
385
+ MaterialDynamicColors,
27
386
  TonalPalette
28
387
  } from "@material/material-color-utilities";
29
- import { kebabCase, startCase, upperFirst } from "lodash-es";
388
+ var MTB_TONES = [
389
+ 0,
390
+ 5,
391
+ 10,
392
+ 15,
393
+ 20,
394
+ 25,
395
+ 30,
396
+ 35,
397
+ 40,
398
+ 50,
399
+ 60,
400
+ 70,
401
+ 80,
402
+ 90,
403
+ 95,
404
+ 98,
405
+ 99,
406
+ 100
407
+ ];
408
+ var FIXTURE_TOKEN_ORDER = [
409
+ "primary",
410
+ "surfaceTint",
411
+ "onPrimary",
412
+ "primaryContainer",
413
+ "onPrimaryContainer",
414
+ "secondary",
415
+ "onSecondary",
416
+ "secondaryContainer",
417
+ "onSecondaryContainer",
418
+ "tertiary",
419
+ "onTertiary",
420
+ "tertiaryContainer",
421
+ "onTertiaryContainer",
422
+ "error",
423
+ "onError",
424
+ "errorContainer",
425
+ "onErrorContainer",
426
+ "background",
427
+ "onBackground",
428
+ "surface",
429
+ "onSurface",
430
+ "surfaceVariant",
431
+ "onSurfaceVariant",
432
+ "outline",
433
+ "outlineVariant",
434
+ "shadow",
435
+ "scrim",
436
+ "inverseSurface",
437
+ "inverseOnSurface",
438
+ "inversePrimary",
439
+ "primaryFixed",
440
+ "onPrimaryFixed",
441
+ "primaryFixedDim",
442
+ "onPrimaryFixedVariant",
443
+ "secondaryFixed",
444
+ "onSecondaryFixed",
445
+ "secondaryFixedDim",
446
+ "onSecondaryFixedVariant",
447
+ "tertiaryFixed",
448
+ "onTertiaryFixed",
449
+ "tertiaryFixedDim",
450
+ "onTertiaryFixedVariant",
451
+ "surfaceDim",
452
+ "surfaceBright",
453
+ "surfaceContainerLowest",
454
+ "surfaceContainerLow",
455
+ "surfaceContainer",
456
+ "surfaceContainerHigh",
457
+ "surfaceContainerHighest"
458
+ ];
459
+ function buildJson(ctx) {
460
+ const {
461
+ hexSource,
462
+ sourceHct,
463
+ effectiveSourceArgb,
464
+ primaryHct,
465
+ SchemeClass,
466
+ scheme,
467
+ primary,
468
+ secondary,
469
+ tertiary,
470
+ error,
471
+ neutral,
472
+ neutralVariant,
473
+ hexCustomColors
474
+ } = ctx;
475
+ const neuHct = neutral ? Hct.fromInt(argbFromHex(neutral)) : sourceHct;
476
+ const nvHct = neutralVariant ? Hct.fromInt(argbFromHex(neutralVariant)) : sourceHct;
477
+ const rawPalettes = {
478
+ primary: TonalPalette.fromInt(effectiveSourceArgb),
479
+ secondary: secondary ? TonalPalette.fromInt(argbFromHex(secondary)) : TonalPalette.fromHueAndChroma(sourceHct.hue, sourceHct.chroma / 3),
480
+ tertiary: tertiary ? TonalPalette.fromInt(argbFromHex(tertiary)) : TonalPalette.fromHueAndChroma(
481
+ (sourceHct.hue + 60) % 360,
482
+ sourceHct.chroma / 2
483
+ ),
484
+ neutral: TonalPalette.fromHueAndChroma(
485
+ neuHct.hue,
486
+ Math.min(neuHct.chroma / 12, 4)
487
+ ),
488
+ "neutral-variant": TonalPalette.fromHueAndChroma(
489
+ nvHct.hue,
490
+ Math.min(nvHct.chroma / 6, 8)
491
+ )
492
+ };
493
+ function buildJsonSchemes() {
494
+ function extractSchemeColors(scheme2, backgroundScheme) {
495
+ const colors = {};
496
+ for (const tokenName of FIXTURE_TOKEN_ORDER) {
497
+ const dynamicColor = MaterialDynamicColors[tokenName];
498
+ const useScheme = backgroundScheme && (tokenName === "background" || tokenName === "onBackground") ? backgroundScheme : scheme2;
499
+ colors[tokenName] = hexFromArgb4(
500
+ dynamicColor.getArgb(useScheme)
501
+ ).toUpperCase();
502
+ }
503
+ return colors;
504
+ }
505
+ function resolveOverridePalette(hex, role) {
506
+ if (!hex) return null;
507
+ return new SchemeClass(Hct.fromInt(argbFromHex(hex)), false, 0)[role];
508
+ }
509
+ const secPalette = resolveOverridePalette(secondary, "primaryPalette");
510
+ const terPalette = resolveOverridePalette(tertiary, "primaryPalette");
511
+ const errPalette = resolveOverridePalette(error, "primaryPalette");
512
+ const neuPalette = resolveOverridePalette(neutral, "neutralPalette");
513
+ const nvPalette = resolveOverridePalette(
514
+ neutralVariant,
515
+ "neutralVariantPalette"
516
+ );
517
+ const jsonSchemes = {};
518
+ const jsonContrastLevels = [
519
+ { name: "light", isDark: false, contrast: 0 },
520
+ { name: "light-medium-contrast", isDark: false, contrast: 0.5 },
521
+ { name: "light-high-contrast", isDark: false, contrast: 1 },
522
+ { name: "dark", isDark: true, contrast: 0 },
523
+ { name: "dark-medium-contrast", isDark: true, contrast: 0.5 },
524
+ { name: "dark-high-contrast", isDark: true, contrast: 1 }
525
+ ];
526
+ for (const { name, isDark, contrast } of jsonContrastLevels) {
527
+ const baseScheme = new SchemeClass(primaryHct, isDark, contrast);
528
+ const composedScheme = new DynamicScheme({
529
+ sourceColorArgb: effectiveSourceArgb,
530
+ variant: schemeToVariant[scheme],
531
+ contrastLevel: contrast,
532
+ isDark,
533
+ primaryPalette: baseScheme.primaryPalette,
534
+ secondaryPalette: secPalette || baseScheme.secondaryPalette,
535
+ tertiaryPalette: terPalette || baseScheme.tertiaryPalette,
536
+ neutralPalette: neuPalette || baseScheme.neutralPalette,
537
+ neutralVariantPalette: nvPalette || baseScheme.neutralVariantPalette
538
+ });
539
+ if (errPalette) composedScheme.errorPalette = errPalette;
540
+ jsonSchemes[name] = extractSchemeColors(composedScheme, baseScheme);
541
+ }
542
+ return jsonSchemes;
543
+ }
544
+ function rawPalettesToJson() {
545
+ const jsonPalettes = {};
546
+ const RAW_PALETTE_NAMES = [
547
+ "primary",
548
+ "secondary",
549
+ "tertiary",
550
+ "neutral",
551
+ "neutral-variant"
552
+ ];
553
+ for (const name of RAW_PALETTE_NAMES) {
554
+ const palette = rawPalettes[name];
555
+ const tones = {};
556
+ for (const tone of MTB_TONES) {
557
+ tones[tone.toString()] = hexFromArgb4(palette.tone(tone)).toUpperCase();
558
+ }
559
+ jsonPalettes[name] = tones;
560
+ }
561
+ return jsonPalettes;
562
+ }
563
+ function buildCoreColors(opts) {
564
+ const colors = { primary: opts.primary };
565
+ if (opts.secondary) colors.secondary = opts.secondary.toUpperCase();
566
+ if (opts.tertiary) colors.tertiary = opts.tertiary.toUpperCase();
567
+ if (opts.error) colors.error = opts.error.toUpperCase();
568
+ if (opts.neutral) colors.neutral = opts.neutral.toUpperCase();
569
+ if (opts.neutralVariant)
570
+ colors.neutralVariant = opts.neutralVariant.toUpperCase();
571
+ return colors;
572
+ }
573
+ const seed = hexSource.toUpperCase();
574
+ const coreColors = buildCoreColors({
575
+ primary: (primary || hexSource).toUpperCase(),
576
+ secondary,
577
+ tertiary,
578
+ error,
579
+ neutral,
580
+ neutralVariant
581
+ });
582
+ const extendedColors = hexCustomColors.map((c) => ({
583
+ name: c.name,
584
+ color: c.hex.toUpperCase(),
585
+ description: "",
586
+ harmonized: c.blend ?? DEFAULT_BLEND
587
+ }));
588
+ return {
589
+ seed,
590
+ coreColors,
591
+ extendedColors,
592
+ schemes: buildJsonSchemes(),
593
+ palettes: rawPalettesToJson()
594
+ };
595
+ }
596
+
597
+ // src/lib/builder.tailwind.ts
598
+ import { kebabCase as kebabCase3 } from "lodash-es";
599
+ var SHADE_TO_TONE = [
600
+ [50, 95],
601
+ [100, 90],
602
+ [200, 80],
603
+ [300, 70],
604
+ [400, 60],
605
+ [500, 50],
606
+ [600, 40],
607
+ [700, 30],
608
+ [800, 20],
609
+ [900, 10],
610
+ [950, 5]
611
+ ];
612
+ var CORE_PALETTES = [
613
+ "primary",
614
+ "secondary",
615
+ "tertiary",
616
+ "error",
617
+ "neutral",
618
+ "neutral-variant"
619
+ ];
620
+ var SHADCN_MAPPING = [
621
+ ["--background", "surface"],
622
+ ["--foreground", "on-surface"],
623
+ ["--card", "surface-container-low"],
624
+ ["--card-foreground", "on-surface"],
625
+ ["--popover", "surface-container-high"],
626
+ ["--popover-foreground", "on-surface"],
627
+ ["--primary", "primary"],
628
+ ["--primary-foreground", "on-primary"],
629
+ ["--secondary", "secondary-container"],
630
+ ["--secondary-foreground", "on-secondary-container"],
631
+ ["--muted", "surface-container-highest"],
632
+ ["--muted-foreground", "on-surface-variant"],
633
+ ["--accent", "secondary-container"],
634
+ ["--accent-foreground", "on-secondary-container"],
635
+ ["--destructive", "error"],
636
+ ["--border", "outline-variant"],
637
+ ["--input", "outline"],
638
+ ["--ring", "primary"],
639
+ ["--chart-1", "primary-fixed"],
640
+ ["--chart-2", "secondary-fixed"],
641
+ ["--chart-3", "tertiary-fixed"],
642
+ ["--chart-4", "primary-fixed-dim"],
643
+ ["--chart-5", "secondary-fixed-dim"],
644
+ ["--sidebar", "surface-container-low"],
645
+ ["--sidebar-foreground", "on-surface"],
646
+ ["--sidebar-primary", "primary"],
647
+ ["--sidebar-primary-foreground", "on-primary"],
648
+ ["--sidebar-accent", "secondary-container"],
649
+ ["--sidebar-accent-foreground", "on-secondary-container"],
650
+ ["--sidebar-border", "outline-variant"],
651
+ ["--sidebar-ring", "primary"]
652
+ ];
653
+ function buildTailwind(ctx, options) {
654
+ const { prefix, mergedColorsLight, hexCustomColors } = ctx;
655
+ const lines = [];
656
+ for (const name of Object.keys(mergedColorsLight)) {
657
+ const kebab = kebabCase3(name);
658
+ lines.push(`--color-${kebab}: var(--${prefix}-sys-color-${kebab});`);
659
+ }
660
+ lines.push("");
661
+ lines.push("/* Shades */");
662
+ for (const palette of CORE_PALETTES) {
663
+ lines.push("");
664
+ for (const [shade, tone] of SHADE_TO_TONE) {
665
+ lines.push(
666
+ `--color-${palette}-${shade}: var(--${prefix}-ref-palette-${palette}-${tone});`
667
+ );
668
+ }
669
+ }
670
+ if (hexCustomColors.length > 0) {
671
+ for (const customColor of hexCustomColors) {
672
+ const kebab = kebabCase3(customColor.name);
673
+ lines.push("");
674
+ for (const [shade, tone] of SHADE_TO_TONE) {
675
+ lines.push(
676
+ `--color-${kebab}-${shade}: var(--${prefix}-ref-palette-${kebab}-${tone});`
677
+ );
678
+ }
679
+ }
680
+ }
681
+ let output = `@theme inline {
682
+ ${lines.join("\n ")}
683
+ }
684
+ `;
685
+ if (options?.shadcn) {
686
+ const shadcnLines = SHADCN_MAPPING.map(
687
+ ([shadcnVar, m3Token]) => `${shadcnVar}: var(--${prefix}-sys-color-${m3Token});`
688
+ );
689
+ output += `
690
+ :root,
691
+ .dark {
692
+ ${shadcnLines.join("\n ")}
693
+ }
694
+ `;
695
+ }
696
+ return output;
697
+ }
698
+
699
+ // src/lib/builder.ts
30
700
  var schemeNames = [
31
701
  "tonalSpot",
32
702
  "monochrome",
@@ -80,26 +750,6 @@ var STANDARD_TONES = [
80
750
  99,
81
751
  100
82
752
  ];
83
- var MTB_TONES = [
84
- 0,
85
- 5,
86
- 10,
87
- 15,
88
- 20,
89
- 25,
90
- 30,
91
- 35,
92
- 40,
93
- 50,
94
- 60,
95
- 70,
96
- 80,
97
- 90,
98
- 95,
99
- 98,
100
- 99,
101
- 100
102
- ];
103
753
  var Variant = {
104
754
  MONOCHROME: 0,
105
755
  NEUTRAL: 1,
@@ -171,9 +821,21 @@ var tokenDescriptions = {
171
821
  tertiaryFixed: "Fixed tertiary color that stays the same in light and dark themes.",
172
822
  tertiaryFixedDim: "Dimmed variant of the fixed tertiary color for lower emphasis."
173
823
  };
174
- var tokenNames = Object.keys(
175
- tokenDescriptions
176
- );
824
+ function isTokenName(key) {
825
+ return key in tokenDescriptions;
826
+ }
827
+ var tokenNames = Object.keys(tokenDescriptions).filter(isTokenName);
828
+ function deriveCustomPaletteName(tokenName, allPaletteNamesKebab) {
829
+ let baseName = tokenName;
830
+ if (/^on[A-Z]/.test(baseName) && baseName.length > 2) {
831
+ baseName = baseName.charAt(2).toLowerCase() + baseName.slice(3);
832
+ }
833
+ if (baseName.endsWith("Container")) {
834
+ baseName = baseName.slice(0, -"Container".length);
835
+ }
836
+ const kebab = kebabCase4(baseName);
837
+ return allPaletteNamesKebab.has(kebab) ? kebab : void 0;
838
+ }
177
839
  function toRecord(arr, getEntry) {
178
840
  return Object.fromEntries(arr.map(getEntry));
179
841
  }
@@ -188,58 +850,55 @@ function getPalette(palettes, colorName) {
188
850
  }
189
851
  function mergeBaseAndCustomColors(scheme, customColors, colorPalettes) {
190
852
  const baseVars = toRecord(tokenNames, (tokenName) => {
191
- const dynamicColor = MaterialDynamicColors[tokenName];
853
+ const dynamicColor = MaterialDynamicColors2[tokenName];
192
854
  const argb = dynamicColor.getArgb(scheme);
193
855
  return [tokenName, argb];
194
856
  });
195
857
  const customVars = {};
196
858
  customColors.forEach((color) => {
197
859
  const colorname = color.name;
198
- const getPaletteForColor = (s) => getPalette(colorPalettes, colorname);
199
- const getTone = (baseTone) => (_s) => {
200
- return baseTone;
201
- };
860
+ const getPaletteForColor = () => getPalette(colorPalettes, colorname);
202
861
  const colorDynamicColor = new DynamicColor(
203
862
  colorname,
204
863
  getPaletteForColor,
205
- (s) => getTone(s.isDark ? 80 : 40)(s),
864
+ (s) => s.isDark ? 80 : 40,
206
865
  // Main color: lighter in dark mode, darker in light mode
207
866
  true
208
867
  // background
209
868
  );
210
869
  const onColorDynamicColor = new DynamicColor(
211
- `on${upperFirst(colorname)}`,
870
+ `on${upperFirst2(colorname)}`,
212
871
  getPaletteForColor,
213
- (s) => getTone(s.isDark ? 20 : 100)(s),
872
+ (s) => s.isDark ? 20 : 100,
214
873
  // Text on main color: high contrast (dark on light, light on dark)
215
874
  false
216
875
  );
217
876
  const containerDynamicColor = new DynamicColor(
218
877
  `${colorname}Container`,
219
878
  getPaletteForColor,
220
- (s) => getTone(s.isDark ? 30 : 90)(s),
879
+ (s) => s.isDark ? 30 : 90,
221
880
  // Container: subtle variant (darker in dark mode, lighter in light mode)
222
881
  true
223
882
  // background
224
883
  );
225
884
  const onContainerDynamicColor = new DynamicColor(
226
- `on${upperFirst(colorname)}Container`,
885
+ `on${upperFirst2(colorname)}Container`,
227
886
  getPaletteForColor,
228
- (s) => getTone(s.isDark ? 90 : 30)(s),
887
+ (s) => s.isDark ? 90 : 30,
229
888
  // Text on container: high contrast against container background
230
889
  false
231
890
  );
232
891
  customVars[colorname] = colorDynamicColor.getArgb(scheme);
233
- customVars[`on${upperFirst(colorname)}`] = onColorDynamicColor.getArgb(scheme);
892
+ customVars[`on${upperFirst2(colorname)}`] = onColorDynamicColor.getArgb(scheme);
234
893
  customVars[`${colorname}Container`] = containerDynamicColor.getArgb(scheme);
235
- customVars[`on${upperFirst(colorname)}Container`] = onContainerDynamicColor.getArgb(scheme);
894
+ customVars[`on${upperFirst2(colorname)}Container`] = onContainerDynamicColor.getArgb(scheme);
236
895
  });
237
896
  return { ...baseVars, ...customVars };
238
897
  }
239
898
  function createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonization) {
240
- const colorArgb = argbFromHex(colorDef.hex);
899
+ const colorArgb = argbFromHex2(colorDef.hex);
241
900
  const harmonizedArgb = colorDef.blend ? Blend.harmonize(colorArgb, effectiveSourceForHarmonization) : colorArgb;
242
- const hct = Hct.fromInt(harmonizedArgb);
901
+ const hct = Hct2.fromInt(harmonizedArgb);
243
902
  let targetChroma;
244
903
  if (colorDef.core && colorDef.chromaSource) {
245
904
  if (colorDef.chromaSource === "neutral") {
@@ -252,7 +911,25 @@ function createColorPalette(colorDef, baseScheme, effectiveSourceForHarmonizatio
252
911
  } else {
253
912
  targetChroma = baseScheme.primaryPalette.chroma;
254
913
  }
255
- return TonalPalette.fromHueAndChroma(hct.hue, targetChroma);
914
+ return TonalPalette2.fromHueAndChroma(hct.hue, targetChroma);
915
+ }
916
+ function buildTokenToPaletteMap(schemePalettes, scheme) {
917
+ const result = {};
918
+ for (const propName of Object.getOwnPropertyNames(MaterialDynamicColors2)) {
919
+ const dc = Object.getOwnPropertyDescriptor(
920
+ MaterialDynamicColors2,
921
+ propName
922
+ )?.value;
923
+ if (!(dc instanceof DynamicColor)) continue;
924
+ const palette = dc.palette(scheme);
925
+ for (const [palName, pal] of schemePalettes) {
926
+ if (palette === pal) {
927
+ result[propName] = palName;
928
+ break;
929
+ }
930
+ }
931
+ }
932
+ return result;
256
933
  }
257
934
  function builder(hexSource, {
258
935
  scheme = DEFAULT_SCHEME,
@@ -266,13 +943,13 @@ function builder(hexSource, {
266
943
  customColors: hexCustomColors = DEFAULT_CUSTOM_COLORS,
267
944
  prefix = DEFAULT_PREFIX
268
945
  } = {}) {
269
- const sourceArgb = argbFromHex(hexSource);
270
- const sourceHct = Hct.fromInt(sourceArgb);
946
+ const sourceArgb = argbFromHex2(hexSource);
947
+ const sourceHct = Hct2.fromInt(sourceArgb);
271
948
  const effectiveSource = primary || hexSource;
272
- const effectiveSourceArgb = argbFromHex(effectiveSource);
273
- const effectiveSourceForHarmonization = primary ? argbFromHex(primary) : sourceArgb;
949
+ const effectiveSourceArgb = argbFromHex2(effectiveSource);
950
+ const effectiveSourceForHarmonization = primary ? argbFromHex2(primary) : sourceArgb;
274
951
  const SchemeClass = schemesMap[scheme];
275
- const primaryHct = Hct.fromInt(effectiveSourceArgb);
952
+ const primaryHct = Hct2.fromInt(effectiveSourceArgb);
276
953
  const baseScheme = new SchemeClass(primaryHct, false, contrast);
277
954
  const allColors = [
278
955
  // Core colors (hex may be undefined)
@@ -337,8 +1014,8 @@ function builder(hexSource, {
337
1014
  neutralPalette: colorPalettes["neutral"] || baseScheme.neutralPalette,
338
1015
  neutralVariantPalette: colorPalettes["neutralVariant"] || baseScheme.neutralVariantPalette
339
1016
  };
340
- const lightScheme = new DynamicScheme({ ...schemeConfig, isDark: false });
341
- const darkScheme = new DynamicScheme({ ...schemeConfig, isDark: true });
1017
+ const lightScheme = new DynamicScheme2({ ...schemeConfig, isDark: false });
1018
+ const darkScheme = new DynamicScheme2({ ...schemeConfig, isDark: true });
342
1019
  const errorPalette = colorPalettes["error"];
343
1020
  if (errorPalette) {
344
1021
  lightScheme.errorPalette = errorPalette;
@@ -359,7 +1036,7 @@ function builder(hexSource, {
359
1036
  const customColors = definedColors.filter((c) => !c.core).map((c) => ({
360
1037
  name: c.name,
361
1038
  blend: c.blend ?? DEFAULT_BLEND,
362
- value: argbFromHex(c.hex)
1039
+ value: argbFromHex2(c.hex)
363
1040
  }));
364
1041
  const mergedColorsLight = mergeBaseAndCustomColors(
365
1042
  lightScheme,
@@ -379,387 +1056,36 @@ function builder(hexSource, {
379
1056
  ["neutral", lightScheme.neutralPalette],
380
1057
  ["neutral-variant", lightScheme.neutralVariantPalette]
381
1058
  ];
382
- function buildTokenToPaletteMap(schemePalettes2, scheme2) {
383
- const result = {};
384
- for (const propName of Object.getOwnPropertyNames(MaterialDynamicColors)) {
385
- const dc = MaterialDynamicColors[propName];
386
- if (!(dc instanceof DynamicColor)) continue;
387
- const palette = dc.palette(scheme2);
388
- for (const [palName, pal] of schemePalettes2) {
389
- if (palette === pal) {
390
- result[propName] = palName;
391
- break;
392
- }
393
- }
394
- }
395
- return result;
396
- }
397
1059
  const tokenToPalette = buildTokenToPaletteMap(schemePalettes, lightScheme);
398
- const allPaletteNamesKebab = new Set(Object.keys(allPalettes).map(kebabCase));
399
- function deriveCustomPaletteName(tokenName) {
400
- let baseName = tokenName;
401
- if (/^on[A-Z]/.test(baseName) && baseName.length > 2) {
402
- baseName = baseName.charAt(2).toLowerCase() + baseName.slice(3);
403
- }
404
- if (baseName.endsWith("Container")) {
405
- baseName = baseName.slice(0, -"Container".length);
406
- }
407
- const kebab = kebabCase(baseName);
408
- return allPaletteNamesKebab.has(kebab) ? kebab : void 0;
409
- }
1060
+ const allPaletteNamesKebab = new Set(Object.keys(allPalettes).map(kebabCase4));
1061
+ const ctx = {
1062
+ hexSource,
1063
+ prefix,
1064
+ scheme,
1065
+ primary,
1066
+ secondary,
1067
+ tertiary,
1068
+ neutral,
1069
+ neutralVariant,
1070
+ error,
1071
+ hexCustomColors,
1072
+ sourceHct,
1073
+ effectiveSourceArgb,
1074
+ primaryHct,
1075
+ SchemeClass,
1076
+ allPalettes,
1077
+ mergedColorsLight,
1078
+ mergedColorsDark,
1079
+ tokenToPalette,
1080
+ allPaletteNamesKebab
1081
+ };
410
1082
  return {
411
- //
412
- // ██████ ███████ ███████
413
- // ██ ██ ██
414
- // ██ ███████ ███████
415
- // ██ ██ ██
416
- // ██████ ███████ ███████
417
- //
418
- toCss() {
419
- function buildRefPaletteLookup() {
420
- const lookup = {};
421
- for (const [name, palette] of Object.entries(allPalettes)) {
422
- const paletteName = kebabCase(name);
423
- for (const tone of STANDARD_TONES) {
424
- const hex = hexFromArgb(palette.tone(tone));
425
- if (!lookup[hex]) lookup[hex] = [];
426
- lookup[hex].push({ paletteName, tone });
427
- }
428
- }
429
- return lookup;
430
- }
431
- function sysColorVar(colorName, colorValue, lookup) {
432
- const name = `--${prefix}-sys-color-${kebabCase(colorName)}`;
433
- const hex = hexFromArgb(colorValue);
434
- const matches = lookup[hex];
435
- if (matches && matches.length > 0) {
436
- const preferred = tokenToPalette[colorName] ?? deriveCustomPaletteName(colorName);
437
- const match = (preferred ? matches.find((m) => m.paletteName === preferred) : void 0) ?? matches[0];
438
- return `${name}:var(--${prefix}-ref-palette-${match.paletteName}-${match.tone});`;
439
- }
440
- return `${name}:${hex};`;
441
- }
442
- function toCssVars(mergedColors, lookup) {
443
- return Object.entries(mergedColors).map(([name, value]) => sysColorVar(name, value, lookup)).join(" ");
444
- }
445
- function refPaletteVar(paletteName, tone, colorValue) {
446
- const name = `--${prefix}-ref-palette-${paletteName}-${tone}`;
447
- const value = hexFromArgb(colorValue);
448
- return `${name}:${value};`;
449
- }
450
- function generateTonalPaletteVars(paletteName, palette) {
451
- return STANDARD_TONES.map((tone) => {
452
- const color = palette.tone(tone);
453
- return refPaletteVar(paletteName, tone, color);
454
- }).join(" ");
455
- }
456
- function generateTonalVars() {
457
- return Object.entries(allPalettes).map(
458
- ([name, palette]) => generateTonalPaletteVars(kebabCase(name), palette)
459
- ).join(" ");
460
- }
461
- const refPaletteLookup = buildRefPaletteLookup();
462
- const lightVars = toCssVars(mergedColorsLight, refPaletteLookup);
463
- const darkVars = toCssVars(mergedColorsDark, refPaletteLookup);
464
- const tonalVars = generateTonalVars();
465
- return `
466
- :root { ${lightVars} ${tonalVars} }
467
- .dark { ${darkVars} ${tonalVars} }
468
- `;
469
- },
470
- //
471
- // ██ ███████ ██████ ███ ██
472
- // ██ ██ ██ ██ ████ ██
473
- // ██ ███████ ██ ██ ██ ██ ██
474
- // ██ ██ ██ ██ ██ ██ ██ ██
475
- // █████ ███████ ██████ ██ ████
476
- //
477
- toJson() {
478
- const fixtureTokenOrder = [
479
- "primary",
480
- "surfaceTint",
481
- "onPrimary",
482
- "primaryContainer",
483
- "onPrimaryContainer",
484
- "secondary",
485
- "onSecondary",
486
- "secondaryContainer",
487
- "onSecondaryContainer",
488
- "tertiary",
489
- "onTertiary",
490
- "tertiaryContainer",
491
- "onTertiaryContainer",
492
- "error",
493
- "onError",
494
- "errorContainer",
495
- "onErrorContainer",
496
- "background",
497
- "onBackground",
498
- "surface",
499
- "onSurface",
500
- "surfaceVariant",
501
- "onSurfaceVariant",
502
- "outline",
503
- "outlineVariant",
504
- "shadow",
505
- "scrim",
506
- "inverseSurface",
507
- "inverseOnSurface",
508
- "inversePrimary",
509
- "primaryFixed",
510
- "onPrimaryFixed",
511
- "primaryFixedDim",
512
- "onPrimaryFixedVariant",
513
- "secondaryFixed",
514
- "onSecondaryFixed",
515
- "secondaryFixedDim",
516
- "onSecondaryFixedVariant",
517
- "tertiaryFixed",
518
- "onTertiaryFixed",
519
- "tertiaryFixedDim",
520
- "onTertiaryFixedVariant",
521
- "surfaceDim",
522
- "surfaceBright",
523
- "surfaceContainerLowest",
524
- "surfaceContainerLow",
525
- "surfaceContainer",
526
- "surfaceContainerHigh",
527
- "surfaceContainerHighest"
528
- ];
529
- const neuHct = neutral ? Hct.fromInt(argbFromHex(neutral)) : sourceHct;
530
- const nvHct = neutralVariant ? Hct.fromInt(argbFromHex(neutralVariant)) : sourceHct;
531
- const rawPalettes = {
532
- primary: TonalPalette.fromInt(effectiveSourceArgb),
533
- secondary: secondary ? TonalPalette.fromInt(argbFromHex(secondary)) : TonalPalette.fromHueAndChroma(sourceHct.hue, sourceHct.chroma / 3),
534
- tertiary: tertiary ? TonalPalette.fromInt(argbFromHex(tertiary)) : TonalPalette.fromHueAndChroma(
535
- (sourceHct.hue + 60) % 360,
536
- sourceHct.chroma / 2
537
- ),
538
- neutral: TonalPalette.fromHueAndChroma(
539
- neuHct.hue,
540
- Math.min(neuHct.chroma / 12, 4)
541
- ),
542
- "neutral-variant": TonalPalette.fromHueAndChroma(
543
- nvHct.hue,
544
- Math.min(nvHct.chroma / 6, 8)
545
- )
546
- };
547
- function buildJsonSchemes() {
548
- function extractSchemeColors(scheme2, backgroundScheme) {
549
- const colors = {};
550
- for (const tokenName of fixtureTokenOrder) {
551
- const dynamicColor = MaterialDynamicColors[tokenName];
552
- const useScheme = backgroundScheme && (tokenName === "background" || tokenName === "onBackground") ? backgroundScheme : scheme2;
553
- colors[tokenName] = hexFromArgb(
554
- dynamicColor.getArgb(useScheme)
555
- ).toUpperCase();
556
- }
557
- return colors;
558
- }
559
- function resolveOverridePalette(hex, role) {
560
- if (!hex) return null;
561
- return new SchemeClass(Hct.fromInt(argbFromHex(hex)), false, 0)[role];
562
- }
563
- const secPalette = resolveOverridePalette(secondary, "primaryPalette");
564
- const terPalette = resolveOverridePalette(tertiary, "primaryPalette");
565
- const errPalette = resolveOverridePalette(error, "primaryPalette");
566
- const neuPalette = resolveOverridePalette(neutral, "neutralPalette");
567
- const nvPalette = resolveOverridePalette(
568
- neutralVariant,
569
- "neutralVariantPalette"
570
- );
571
- const jsonSchemes = {};
572
- const jsonContrastLevels = [
573
- { name: "light", isDark: false, contrast: 0 },
574
- { name: "light-medium-contrast", isDark: false, contrast: 0.5 },
575
- { name: "light-high-contrast", isDark: false, contrast: 1 },
576
- { name: "dark", isDark: true, contrast: 0 },
577
- { name: "dark-medium-contrast", isDark: true, contrast: 0.5 },
578
- { name: "dark-high-contrast", isDark: true, contrast: 1 }
579
- ];
580
- for (const { name, isDark, contrast: contrast2 } of jsonContrastLevels) {
581
- const baseScheme2 = new SchemeClass(primaryHct, isDark, contrast2);
582
- const composedScheme = new DynamicScheme({
583
- sourceColorArgb: effectiveSourceArgb,
584
- variant: schemeToVariant[scheme],
585
- contrastLevel: contrast2,
586
- isDark,
587
- primaryPalette: baseScheme2.primaryPalette,
588
- secondaryPalette: secPalette || baseScheme2.secondaryPalette,
589
- tertiaryPalette: terPalette || baseScheme2.tertiaryPalette,
590
- neutralPalette: neuPalette || baseScheme2.neutralPalette,
591
- neutralVariantPalette: nvPalette || baseScheme2.neutralVariantPalette
592
- });
593
- if (errPalette) composedScheme.errorPalette = errPalette;
594
- jsonSchemes[name] = extractSchemeColors(composedScheme, baseScheme2);
595
- }
596
- return jsonSchemes;
597
- }
598
- function rawPalettesToJson() {
599
- const jsonPalettes = {};
600
- const RAW_PALETTE_NAMES = [
601
- "primary",
602
- "secondary",
603
- "tertiary",
604
- "neutral",
605
- "neutral-variant"
606
- ];
607
- for (const name of RAW_PALETTE_NAMES) {
608
- const palette = rawPalettes[name];
609
- const tones = {};
610
- for (const tone of MTB_TONES) {
611
- tones[tone.toString()] = hexFromArgb(
612
- palette.tone(tone)
613
- ).toUpperCase();
614
- }
615
- jsonPalettes[name] = tones;
616
- }
617
- return jsonPalettes;
618
- }
619
- function buildCoreColors(opts) {
620
- const colors = { primary: opts.primary };
621
- if (opts.secondary) colors.secondary = opts.secondary.toUpperCase();
622
- if (opts.tertiary) colors.tertiary = opts.tertiary.toUpperCase();
623
- if (opts.error) colors.error = opts.error.toUpperCase();
624
- if (opts.neutral) colors.neutral = opts.neutral.toUpperCase();
625
- if (opts.neutralVariant)
626
- colors.neutralVariant = opts.neutralVariant.toUpperCase();
627
- return colors;
628
- }
629
- const seed = hexSource.toUpperCase();
630
- const coreColors = buildCoreColors({
631
- primary: (primary || hexSource).toUpperCase(),
632
- secondary,
633
- tertiary,
634
- error,
635
- neutral,
636
- neutralVariant
637
- });
638
- const extendedColors = hexCustomColors.map((c) => ({
639
- name: c.name,
640
- color: c.hex.toUpperCase(),
641
- description: "",
642
- harmonized: c.blend ?? DEFAULT_BLEND
643
- }));
644
- return {
645
- seed,
646
- coreColors,
647
- extendedColors,
648
- schemes: buildJsonSchemes(),
649
- palettes: rawPalettesToJson()
650
- };
651
- },
652
- //
653
- // ███████ ██ ██████ ███ ███ █████
654
- // ██ ██ ██ ████ ████ ██ ██
655
- // █████ ██ ██ ███ ██ ████ ██ ███████
656
- // ██ ██ ██ ██ ██ ██ ██ ██ ██
657
- // ██ ██ ██████ ██ ██ ██ ██
658
- //
659
- toFigmaTokens() {
660
- function argbToFigmaColorValue(argb) {
661
- return {
662
- colorSpace: "srgb",
663
- components: [
664
- redFromArgb(argb) / 255,
665
- greenFromArgb(argb) / 255,
666
- blueFromArgb(argb) / 255
667
- ],
668
- alpha: 1,
669
- hex: hexFromArgb(argb).toUpperCase()
670
- };
671
- }
672
- function figmaToken(argb) {
673
- return {
674
- $type: "color",
675
- $value: argbToFigmaColorValue(argb),
676
- $extensions: {
677
- "com.figma.scopes": ["ALL_SCOPES"],
678
- "com.figma.isOverride": true
679
- }
680
- };
681
- }
682
- function buildRefPaletteTokens() {
683
- const palettes = {};
684
- for (const [name, palette] of Object.entries(allPalettes)) {
685
- const tones = {};
686
- for (const tone of STANDARD_TONES) {
687
- const argb = palette.tone(tone);
688
- tones[tone.toString()] = figmaToken(argb);
689
- }
690
- palettes[startCase(name)] = tones;
691
- }
692
- return palettes;
693
- }
694
- function findToneInPalette(hex, paletteName, tones) {
695
- for (const [tone, token] of Object.entries(tones)) {
696
- if (token.$value.hex === hex) {
697
- return `{ref.palette.${paletteName}.${tone}}`;
698
- }
699
- }
700
- return null;
701
- }
702
- function findAlias(hex, tokenName, refPalettes2) {
703
- const preferredPaletteKebab = tokenToPalette[tokenName] ?? deriveCustomPaletteName(tokenName);
704
- const preferredPalette = preferredPaletteKebab ? startCase(preferredPaletteKebab) : void 0;
705
- if (preferredPalette && refPalettes2[preferredPalette]) {
706
- const match = findToneInPalette(
707
- hex,
708
- preferredPalette,
709
- refPalettes2[preferredPalette]
710
- );
711
- if (match) return match;
712
- }
713
- for (const [palName, tones] of Object.entries(refPalettes2)) {
714
- const match = findToneInPalette(hex, palName, tones);
715
- if (match) return match;
716
- }
717
- return null;
718
- }
719
- function resolveModeValue(argb, tokenName, refPalettes2) {
720
- const hex = hexFromArgb(argb).toUpperCase();
721
- return findAlias(hex, tokenName, refPalettes2) ?? argbToFigmaColorValue(argb);
722
- }
723
- function buildSysColorTokens(mergedColors, refPalettes2) {
724
- const tokens = {};
725
- for (const [name, argb] of Object.entries(mergedColors)) {
726
- const description = tokenDescriptions[name];
727
- const cssVar = `--${prefix}-sys-color-${kebabCase(name)}`;
728
- const value = resolveModeValue(argb, name, refPalettes2);
729
- tokens[startCase(name)] = {
730
- $type: "color",
731
- $value: value,
732
- ...description ? { $description: description } : {},
733
- $extensions: {
734
- "com.figma.scopes": ["ALL_SCOPES"],
735
- "css.variable": cssVar
736
- }
737
- };
738
- }
739
- return tokens;
740
- }
741
- const refPalettes = buildRefPaletteTokens();
742
- function buildModeFile(modeName, mergedColors) {
743
- return {
744
- ref: {
745
- palette: refPalettes
746
- },
747
- sys: {
748
- color: buildSysColorTokens(mergedColors, refPalettes)
749
- },
750
- $extensions: {
751
- "com.figma.modeName": modeName
752
- }
753
- };
754
- }
755
- return {
756
- "Light.tokens.json": buildModeFile("Light", mergedColorsLight),
757
- "Dark.tokens.json": buildModeFile("Dark", mergedColorsDark)
758
- };
759
- },
760
- //
761
- // API
762
- //
1083
+ toCss: () => buildCss(ctx),
1084
+ toJson: () => buildJson(ctx),
1085
+ toFigmaVariables: () => buildFigmaVariables(ctx),
1086
+ toFigmaTokens: () => buildFigmaTokens(ctx),
1087
+ toTailwind: (options) => buildTailwind(ctx, options),
1088
+ toFlutter: () => buildFlutter(ctx),
763
1089
  mergedColorsLight,
764
1090
  mergedColorsDark,
765
1091
  allPalettes
@@ -767,6 +1093,13 @@ function builder(hexSource, {
767
1093
  }
768
1094
 
769
1095
  // src/cli.ts
1096
+ var customColorSchema = z.array(
1097
+ z.object({
1098
+ name: z.string(),
1099
+ hex: z.string(),
1100
+ blend: z.boolean().default(DEFAULT_BLEND)
1101
+ })
1102
+ );
770
1103
  var program = new Command();
771
1104
  program.name("material-theme-builder").description("Generate a color theme from a source color").argument("<source>", "Source color in hex format (e.g. #6750A4)").addOption(
772
1105
  new Option("--scheme <name>", "Color scheme variant").choices(schemeNames).default(DEFAULT_SCHEME)
@@ -778,24 +1111,26 @@ program.name("material-theme-builder").description("Generate a color theme from
778
1111
  ).option("--primary <hex>", "Primary color override").option("--secondary <hex>", "Secondary color override").option("--tertiary <hex>", "Tertiary color override").option("--error <hex>", "Error color override").option("--neutral <hex>", "Neutral color override").option("--neutral-variant <hex>", "Neutral variant color override").option(
779
1112
  "--custom-colors <json>",
780
1113
  `Custom colors as JSON array (e.g. '[{"name":"brand","hex":"#FF5733","blend":true}]')`
781
- ).option("--format <type>", "Output format: json, css, or figma", "figma").option("--output <dir>", "Output directory (required for figma format)").option(
1114
+ ).option(
1115
+ "--format <type>",
1116
+ "Output format: json, css, figma, tailwind, or flutter",
1117
+ "figma"
1118
+ ).option("--output <dir>", "Output directory (required for figma format)").option(
1119
+ "--shadcn",
1120
+ "Append shadcn CSS variable remapping (with --format tailwind)"
1121
+ ).option(
782
1122
  "--prefix <string>",
783
1123
  "CSS variable prefix (e.g. md \u2192 --md-sys-color-*, --md-ref-palette-*)",
784
1124
  DEFAULT_PREFIX
785
1125
  ).action((source, opts) => {
786
1126
  let customColors = [];
787
1127
  if (opts.customColors) {
788
- try {
789
- const parsed = JSON.parse(opts.customColors);
790
- customColors = parsed.map((c) => ({
791
- name: c.name,
792
- hex: c.hex,
793
- blend: c.blend ?? DEFAULT_BLEND
794
- }));
795
- } catch {
1128
+ const result2 = customColorSchema.safeParse(JSON.parse(opts.customColors));
1129
+ if (!result2.success) {
796
1130
  console.error("Error: --custom-colors must be valid JSON");
797
1131
  process.exit(1);
798
1132
  }
1133
+ customColors = result2.data;
799
1134
  }
800
1135
  const result = builder(source, {
801
1136
  scheme: opts.scheme,
@@ -811,6 +1146,10 @@ program.name("material-theme-builder").description("Generate a color theme from
811
1146
  });
812
1147
  if (opts.format === "css") {
813
1148
  process.stdout.write(result.toCss());
1149
+ } else if (opts.format === "tailwind") {
1150
+ process.stdout.write(result.toTailwind({ shadcn: opts.shadcn }));
1151
+ } else if (opts.format === "flutter") {
1152
+ process.stdout.write(result.toFlutter());
814
1153
  } else if (opts.format === "figma") {
815
1154
  const outputDir = opts.output ?? "material-theme";
816
1155
  fs.mkdirSync(outputDir, { recursive: true });