@qw-ui/tokens 0.3.0-next.3 → 0.3.0-next.5
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/README.md +7 -4
- package/dist/color-architecture.d.ts +153 -3
- package/dist/derive/index.d.ts +2 -2
- package/dist/derive/live-css.d.ts +1 -1
- package/dist/derive/reference.d.ts +42 -8
- package/dist/derive/semantic.d.ts +7 -1
- package/dist/design-doc.d.ts +1 -1
- package/dist/figma/color-contract.d.ts +2 -0
- package/dist/figma/color-snapshot.d.ts +4 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +647 -38
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +3 -3
- package/dist/tokens.css +52 -2
- package/dist/tokens.json +83 -3
- package/dist/tokens.live.css +72 -9
- package/dist/tokens.ts +85 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -474,6 +474,41 @@ const T2_OKLCH_STEPS = [
|
|
|
474
474
|
800,
|
|
475
475
|
900
|
|
476
476
|
];
|
|
477
|
+
/**
|
|
478
|
+
* T2 步进锚定 Alpha:锚定 OKLCH 档位实色 + 固定 alpha 的设计校准 rgba
|
|
479
|
+
* (`{seed}/{step}-a{alpha}`,如 `brand/500-a20`)。只声明被 T3 角色消费的条目,
|
|
480
|
+
* 不作为完整刻度生成。
|
|
481
|
+
*/
|
|
482
|
+
const FIGMA_T2_STEP_ALPHA = [
|
|
483
|
+
{
|
|
484
|
+
seed: "brand",
|
|
485
|
+
step: 500,
|
|
486
|
+
alpha: 20
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
seed: "blue",
|
|
490
|
+
step: 600,
|
|
491
|
+
alpha: 20
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
seed: "blue",
|
|
495
|
+
step: 600,
|
|
496
|
+
alpha: 40
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
seed: "red",
|
|
500
|
+
step: 400,
|
|
501
|
+
alpha: 10
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
seed: "red",
|
|
505
|
+
step: 400,
|
|
506
|
+
alpha: 20
|
|
507
|
+
}
|
|
508
|
+
];
|
|
509
|
+
/** T2 `pure` 步进(标准常量 #000000,OLED 画布用)仅提供给 black。 */
|
|
510
|
+
const T2_PURE_STEP = "pure";
|
|
511
|
+
const T2_PURE_SEEDS = ["black"];
|
|
477
512
|
const FIGMA_T2_SOLID_SCALE = {
|
|
478
513
|
brand: {
|
|
479
514
|
50: "#F2F6FF",
|
|
@@ -620,6 +655,11 @@ const FIGMA_T2_SOLID_SCALE = {
|
|
|
620
655
|
900: "#200B00"
|
|
621
656
|
}
|
|
622
657
|
};
|
|
658
|
+
/**
|
|
659
|
+
* Figma「Color Token System」T3 语义角色表(60 个,light/dark 均引用 T2 路径)。
|
|
660
|
+
* 与 Figma 节点 22258:49986 逐条对齐;`effect.shadow.*.color` 为复合阴影的颜色
|
|
661
|
+
* 分量(对应 Figma 变量 `--qw-sem-effect-shadow-*-color`)。
|
|
662
|
+
*/
|
|
623
663
|
const FIGMA_T3_COLOR_ROLES = {
|
|
624
664
|
"surface.base": {
|
|
625
665
|
light: "color.white.base",
|
|
@@ -641,6 +681,26 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
641
681
|
light: "color.blue.50",
|
|
642
682
|
dark: "color.blue.800"
|
|
643
683
|
},
|
|
684
|
+
"surface.canvas": {
|
|
685
|
+
light: "color.white.base",
|
|
686
|
+
dark: "color.black.pure"
|
|
687
|
+
},
|
|
688
|
+
"surface.sunken": {
|
|
689
|
+
light: "color.gray.100",
|
|
690
|
+
dark: "color.gray.800"
|
|
691
|
+
},
|
|
692
|
+
"surface.selected": {
|
|
693
|
+
light: "color.black.a5",
|
|
694
|
+
dark: "color.white.a10"
|
|
695
|
+
},
|
|
696
|
+
"surface.inverse": {
|
|
697
|
+
light: "color.black.800",
|
|
698
|
+
dark: "color.black.800"
|
|
699
|
+
},
|
|
700
|
+
"surface.canvas-subtle": {
|
|
701
|
+
light: "color.gray.50",
|
|
702
|
+
dark: "color.black.pure"
|
|
703
|
+
},
|
|
644
704
|
"content.primary": {
|
|
645
705
|
light: "color.black.base",
|
|
646
706
|
dark: "color.white.base"
|
|
@@ -661,6 +721,30 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
661
721
|
light: "color.gray.400",
|
|
662
722
|
dark: "color.gray.500"
|
|
663
723
|
},
|
|
724
|
+
"content.disabled": {
|
|
725
|
+
light: "color.black.a30",
|
|
726
|
+
dark: "color.white.a30"
|
|
727
|
+
},
|
|
728
|
+
"content.link": {
|
|
729
|
+
light: "color.blue.700",
|
|
730
|
+
dark: "color.blue.400"
|
|
731
|
+
},
|
|
732
|
+
"content.icon-subtle": {
|
|
733
|
+
light: "color.black.a5",
|
|
734
|
+
dark: "color.white.a10"
|
|
735
|
+
},
|
|
736
|
+
"content.inverse": {
|
|
737
|
+
light: "color.white.base",
|
|
738
|
+
dark: "color.black.base"
|
|
739
|
+
},
|
|
740
|
+
"content.on-solid": {
|
|
741
|
+
light: "color.white.base",
|
|
742
|
+
dark: "color.white.base"
|
|
743
|
+
},
|
|
744
|
+
"content.on-warning-subtle": {
|
|
745
|
+
light: "color.yellow.800",
|
|
746
|
+
dark: "color.yellow.200"
|
|
747
|
+
},
|
|
664
748
|
"border.default": {
|
|
665
749
|
light: "color.gray.100",
|
|
666
750
|
dark: "color.gray.700"
|
|
@@ -673,6 +757,18 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
673
757
|
light: "color.brand.base",
|
|
674
758
|
dark: "color.brand.500"
|
|
675
759
|
},
|
|
760
|
+
"border.subtle": {
|
|
761
|
+
light: "color.black.a5",
|
|
762
|
+
dark: "color.white.a5"
|
|
763
|
+
},
|
|
764
|
+
"border.disabled": {
|
|
765
|
+
light: "color.black.a10",
|
|
766
|
+
dark: "color.white.a10"
|
|
767
|
+
},
|
|
768
|
+
"border.on-media": {
|
|
769
|
+
light: "color.white.base",
|
|
770
|
+
dark: "color.white.base"
|
|
771
|
+
},
|
|
676
772
|
"fill.subtle": {
|
|
677
773
|
light: "color.gray.50",
|
|
678
774
|
dark: "color.white.a20"
|
|
@@ -689,6 +785,10 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
689
785
|
light: "color.gray.50",
|
|
690
786
|
dark: "color.gray.900"
|
|
691
787
|
},
|
|
788
|
+
"fill.track": {
|
|
789
|
+
light: "color.black.a10",
|
|
790
|
+
dark: "color.white.a20"
|
|
791
|
+
},
|
|
692
792
|
"action.brand": {
|
|
693
793
|
light: "color.brand.base",
|
|
694
794
|
dark: "color.brand.500"
|
|
@@ -699,12 +799,44 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
699
799
|
},
|
|
700
800
|
"action.selected": {
|
|
701
801
|
light: "color.blue.a20",
|
|
702
|
-
dark: "color.blue.a40"
|
|
802
|
+
dark: "color.blue.600-a40"
|
|
703
803
|
},
|
|
704
804
|
"action.inverse": {
|
|
705
805
|
light: "color.white.base",
|
|
706
806
|
dark: "color.black.base"
|
|
707
807
|
},
|
|
808
|
+
"control-action.brand.subtle": {
|
|
809
|
+
light: "color.brand.a10",
|
|
810
|
+
dark: "color.brand.500-a20"
|
|
811
|
+
},
|
|
812
|
+
"control-action.product.subtle": {
|
|
813
|
+
light: "color.blue.a10",
|
|
814
|
+
dark: "color.blue.600-a20"
|
|
815
|
+
},
|
|
816
|
+
"control-action.danger.default": {
|
|
817
|
+
light: "color.red.400",
|
|
818
|
+
dark: "color.red.400"
|
|
819
|
+
},
|
|
820
|
+
"control-action.danger.subtle": {
|
|
821
|
+
light: "color.red.400-a10",
|
|
822
|
+
dark: "color.red.400-a20"
|
|
823
|
+
},
|
|
824
|
+
"control-action.neutral.subtle": {
|
|
825
|
+
light: "color.black.a5",
|
|
826
|
+
dark: "color.white.a10"
|
|
827
|
+
},
|
|
828
|
+
"control-selection.brand": {
|
|
829
|
+
light: "color.brand.a10",
|
|
830
|
+
dark: "color.brand.500-a20"
|
|
831
|
+
},
|
|
832
|
+
"control-selection.product": {
|
|
833
|
+
light: "color.blue.a10",
|
|
834
|
+
dark: "color.blue.600-a20"
|
|
835
|
+
},
|
|
836
|
+
"selection.text-background": {
|
|
837
|
+
light: "color.blue.a20",
|
|
838
|
+
dark: "color.blue.600-a40"
|
|
839
|
+
},
|
|
708
840
|
"status.success": {
|
|
709
841
|
light: "color.green.base",
|
|
710
842
|
dark: "color.green.base"
|
|
@@ -721,13 +853,53 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
721
853
|
light: "color.blue.base",
|
|
722
854
|
dark: "color.blue.600"
|
|
723
855
|
},
|
|
856
|
+
"status.success-subtle": {
|
|
857
|
+
light: "color.green.a10",
|
|
858
|
+
dark: "color.green.a20"
|
|
859
|
+
},
|
|
860
|
+
"status.warning-subtle": {
|
|
861
|
+
light: "color.yellow.a20",
|
|
862
|
+
dark: "color.yellow.a20"
|
|
863
|
+
},
|
|
864
|
+
"status.danger-subtle": {
|
|
865
|
+
light: "color.red.400-a10",
|
|
866
|
+
dark: "color.red.400-a20"
|
|
867
|
+
},
|
|
868
|
+
"status.info-subtle": {
|
|
869
|
+
light: "color.blue.a10",
|
|
870
|
+
dark: "color.blue.600-a20"
|
|
871
|
+
},
|
|
724
872
|
"overlay.scrim": {
|
|
725
873
|
light: "color.black.a50",
|
|
726
874
|
dark: "color.black.a70"
|
|
727
875
|
},
|
|
728
876
|
"overlay.guide": {
|
|
729
|
-
light: "color.
|
|
730
|
-
dark: "color.
|
|
877
|
+
light: "color.blue.a10",
|
|
878
|
+
dark: "color.blue.600-a20"
|
|
879
|
+
},
|
|
880
|
+
"overlay.media-subtle": {
|
|
881
|
+
light: "color.black.a40",
|
|
882
|
+
dark: "color.black.a40"
|
|
883
|
+
},
|
|
884
|
+
"overlay.media-strong": {
|
|
885
|
+
light: "color.black.a50",
|
|
886
|
+
dark: "color.black.a50"
|
|
887
|
+
},
|
|
888
|
+
shadow: {
|
|
889
|
+
light: "color.black.a10",
|
|
890
|
+
dark: "color.black.a50"
|
|
891
|
+
},
|
|
892
|
+
"effect.shadow.card.color": {
|
|
893
|
+
light: "color.black.a10",
|
|
894
|
+
dark: "color.black.a50"
|
|
895
|
+
},
|
|
896
|
+
"effect.shadow.floating.color": {
|
|
897
|
+
light: "color.black.a10",
|
|
898
|
+
dark: "color.black.a50"
|
|
899
|
+
},
|
|
900
|
+
"effect.shadow.dialog.color": {
|
|
901
|
+
light: "color.black.a10",
|
|
902
|
+
dark: "color.black.a50"
|
|
731
903
|
}
|
|
732
904
|
};
|
|
733
905
|
//#endregion
|
|
@@ -828,11 +1000,11 @@ const cubicBezier = z.tuple([
|
|
|
828
1000
|
const fontStack = z.array(z.string()).min(1);
|
|
829
1001
|
const colorSeedShape = Object.fromEntries(T1_COLOR_KEYS.map((key) => [key, seedColor]));
|
|
830
1002
|
/**
|
|
831
|
-
* SeedSchema — T1 Seed
|
|
1003
|
+
* SeedSchema — T1 Seed 与兼容输入的运行时校验(RFC 0001 §2.1,v4 修订)。
|
|
832
1004
|
*
|
|
833
1005
|
* 五大类目 color/size/typography/motion/effect,字段结构对齐
|
|
834
|
-
* packages/tokens/design/seed.tokens.json
|
|
835
|
-
*
|
|
1006
|
+
* packages/tokens/design/seed.tokens.json。部分已不再驱动 canonical T2 的字段暂时
|
|
1007
|
+
* 保留为兼容输入;新 Reference 不得为了形式完整继续扩充伪 Seed。
|
|
836
1008
|
*
|
|
837
1009
|
* v3 变化:
|
|
838
1010
|
* - 颜色全部结构化(DTCG Color module,支持 srgb/oklch)。
|
|
@@ -881,8 +1053,11 @@ base: z.string() }),
|
|
|
881
1053
|
textWindowsJa: fontStack
|
|
882
1054
|
}),
|
|
883
1055
|
fontWeight: z.object({
|
|
1056
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
884
1057
|
regular: z.number(),
|
|
1058
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
885
1059
|
medium: z.number(),
|
|
1060
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
886
1061
|
semibold: z.number()
|
|
887
1062
|
}),
|
|
888
1063
|
fontSize: z.object({ base: z.string() }),
|
|
@@ -1093,6 +1268,14 @@ function seedAlphaCss(seedColor, alpha) {
|
|
|
1093
1268
|
});
|
|
1094
1269
|
return rgba(hexRgb(seedColorToHex(seedColor)), alpha);
|
|
1095
1270
|
}
|
|
1271
|
+
/** 从 Seed 颜色点分路径("color.brand")取 T1 键。 */
|
|
1272
|
+
function seedKeyOf(seedRef) {
|
|
1273
|
+
return seedRef.split(".")[1];
|
|
1274
|
+
}
|
|
1275
|
+
/** 步进锚定 Alpha 的求值:FIGMA_T2_SOLID_SCALE 档位实色 + alpha → rgba。 */
|
|
1276
|
+
function stepAlphaCss(seed, step, alpha) {
|
|
1277
|
+
return rgba(hexRgb(FIGMA_T2_SOLID_SCALE[seed][step]), alpha);
|
|
1278
|
+
}
|
|
1096
1279
|
function formatFontFamily(list) {
|
|
1097
1280
|
return list.map((f) => /[\s./]/.test(f) ? `'${f}'` : f).join(", ");
|
|
1098
1281
|
}
|
|
@@ -1154,6 +1337,7 @@ function evaluateDerivation(d, seed) {
|
|
|
1154
1337
|
case "oklchRamp": return ramp(seed.color[d.seedRef.split(".")[1]])[d.step];
|
|
1155
1338
|
case "figmaOklchScale": return d.value;
|
|
1156
1339
|
case "seedAlpha": return seedAlphaCss(seedValueAt(seed, d.seedRef), d.alpha);
|
|
1340
|
+
case "stepAlpha": return stepAlphaCss(seedKeyOf(d.seedRef), d.step, d.alpha);
|
|
1157
1341
|
case "alphaScale": {
|
|
1158
1342
|
const step = seedValueAt(seed, d.stepRef);
|
|
1159
1343
|
return rgba(d.channel === "black" ? [
|
|
@@ -1168,7 +1352,7 @@ function evaluateDerivation(d, seed) {
|
|
|
1168
1352
|
}
|
|
1169
1353
|
case "fontStack": return formatFontFamily(seedValueAt(seed, d.seedRef));
|
|
1170
1354
|
case "cubicBezier": return formatCubicBezier(seedValueAt(seed, d.seedRef));
|
|
1171
|
-
case "literal": return d.value;
|
|
1355
|
+
case "literal": return String(d.value);
|
|
1172
1356
|
case "shadowLayer": {
|
|
1173
1357
|
const { offsetX, offsetY, blur, spread } = shadowShapeOf(seed, d.level);
|
|
1174
1358
|
return `${offsetX} ${offsetY} ${blur} ${spread} ${rgba([
|
|
@@ -1189,6 +1373,7 @@ function seedRefsOf(d) {
|
|
|
1189
1373
|
case "oklchRamp":
|
|
1190
1374
|
case "figmaOklchScale":
|
|
1191
1375
|
case "seedAlpha":
|
|
1376
|
+
case "stepAlpha":
|
|
1192
1377
|
case "fontStack":
|
|
1193
1378
|
case "cubicBezier": return [d.seedRef];
|
|
1194
1379
|
case "offsetSteps": return [d.seedRef, d.stepRef];
|
|
@@ -1197,6 +1382,18 @@ function seedRefsOf(d) {
|
|
|
1197
1382
|
case "shadowLayer": return ["effect.shadow.base", "size.opacity.alphaStep"];
|
|
1198
1383
|
}
|
|
1199
1384
|
}
|
|
1385
|
+
/** Derivation AST → 规范来源分类;作为 Figma/DTCG provenance 的唯一映射口。 */
|
|
1386
|
+
function sourceKindOf(d) {
|
|
1387
|
+
switch (d.kind) {
|
|
1388
|
+
case "alias":
|
|
1389
|
+
case "fontStack":
|
|
1390
|
+
case "cubicBezier": return "seed-alias";
|
|
1391
|
+
case "figmaOklchScale":
|
|
1392
|
+
case "stepAlpha": return "design-calibrated";
|
|
1393
|
+
case "literal": return "standard-constant";
|
|
1394
|
+
default: return "seed-derived";
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1200
1397
|
/** 人读派生公式(Token Studio 展示用)。 */
|
|
1201
1398
|
function formatDerivation(d) {
|
|
1202
1399
|
const sign = (n) => n >= 0 ? "+" : "−";
|
|
@@ -1210,20 +1407,24 @@ function formatDerivation(d) {
|
|
|
1210
1407
|
case "offsetSteps": return `= seed.${d.seedRef} ${sign(d.n)} seed.${d.stepRef} × ${Math.abs(d.n)}`;
|
|
1211
1408
|
case "oklchRamp": return `= oklch(seed.${d.seedRef}) @ L ${L_TARGETS[d.step]} · C × ${round2(chromaFactor(L_TARGETS[d.step]))}`;
|
|
1212
1409
|
case "figmaOklchScale": return `= figma Color/qw/t2/${d.seedRef.split(".")[1]}/${d.step}(OKLCH 设计校准)`;
|
|
1410
|
+
case "stepAlpha": return `= figma Color/qw/t2/${d.seedRef.split(".")[1]}/${d.step}-a${round2(d.alpha * 100)}(步进锚定 Alpha)`;
|
|
1213
1411
|
case "seedAlpha": return `= seed.${d.seedRef} / ${round2(d.alpha * 100)}%`;
|
|
1214
1412
|
case "alphaScale": return `= ${d.channel === "black" ? "黑" : "白"} / seed.${d.stepRef} × ${d.multiplier}`;
|
|
1215
|
-
case "literal": return `= ${d.value}
|
|
1413
|
+
case "literal": return `= ${d.value}(canonical 标准常量)`;
|
|
1216
1414
|
case "shadowLayer": {
|
|
1217
1415
|
const f = SHADOW_LEVELS[d.level];
|
|
1218
1416
|
return `= seed.effect.shadow.base 形状 × {offset ${f.offset}, blur ${f.blur}} · 色 = 黑 / alphaStep × ${f.alphaMult}`;
|
|
1219
1417
|
}
|
|
1220
1418
|
}
|
|
1221
1419
|
}
|
|
1222
|
-
function ref(path, derivation) {
|
|
1420
|
+
function ref(path, derivation, metadata = {}) {
|
|
1421
|
+
const { sourceKind = sourceKindOf(derivation), ...details } = metadata;
|
|
1223
1422
|
return {
|
|
1224
1423
|
path,
|
|
1225
1424
|
cssVar: toVar("ref", path),
|
|
1425
|
+
sourceKind,
|
|
1226
1426
|
seedRefs: seedRefsOf(derivation),
|
|
1427
|
+
...details,
|
|
1227
1428
|
derivation
|
|
1228
1429
|
};
|
|
1229
1430
|
}
|
|
@@ -1251,6 +1452,9 @@ function buildColorRampRecipes() {
|
|
|
1251
1452
|
seedRef,
|
|
1252
1453
|
step,
|
|
1253
1454
|
value: FIGMA_T2_SOLID_SCALE[name][step]
|
|
1455
|
+
}, {
|
|
1456
|
+
source: `Figma Color/qw/t2/${name}/${step}`,
|
|
1457
|
+
rationale: "OKLCH solid scale is design-calibrated; the Seed records color-family lineage, not the final step value."
|
|
1254
1458
|
}));
|
|
1255
1459
|
out.push(ref([
|
|
1256
1460
|
"color",
|
|
@@ -1263,6 +1467,35 @@ function buildColorRampRecipes() {
|
|
|
1263
1467
|
}
|
|
1264
1468
|
return out;
|
|
1265
1469
|
}
|
|
1470
|
+
/** 2. Figma T2 补充原语:步进锚定 Alpha(仅被 T3 消费的条目)+ black/pure 标准常量。 */
|
|
1471
|
+
function buildColorExtraRecipes() {
|
|
1472
|
+
const out = [];
|
|
1473
|
+
for (const { seed, step, alpha } of FIGMA_T2_STEP_ALPHA) out.push(ref([
|
|
1474
|
+
"color",
|
|
1475
|
+
seed,
|
|
1476
|
+
`${step}-a${alpha}`
|
|
1477
|
+
], {
|
|
1478
|
+
kind: "stepAlpha",
|
|
1479
|
+
seedRef: `color.${seed}`,
|
|
1480
|
+
step,
|
|
1481
|
+
alpha: alpha / 100
|
|
1482
|
+
}, {
|
|
1483
|
+
source: `Figma Color/qw/t2/${seed}/${step}-a${alpha}`,
|
|
1484
|
+
rationale: "Step-anchored alpha is design-calibrated: both the anchored solid step and the alpha are pinned by the design file."
|
|
1485
|
+
}));
|
|
1486
|
+
out.push(ref([
|
|
1487
|
+
"color",
|
|
1488
|
+
"black",
|
|
1489
|
+
"pure"
|
|
1490
|
+
], {
|
|
1491
|
+
kind: "literal",
|
|
1492
|
+
value: "#000000"
|
|
1493
|
+
}, {
|
|
1494
|
+
source: "CSS Color sRGB endpoint",
|
|
1495
|
+
rationale: "Mathematical black endpoint for OLED canvas; replaces deprecated neutral/1000 semantics; no theme-level generator input."
|
|
1496
|
+
}));
|
|
1497
|
+
return out;
|
|
1498
|
+
}
|
|
1266
1499
|
/** 4. 间距刻度 = spacing.step × N (N=1..8)。 */
|
|
1267
1500
|
function buildSpacingRecipes() {
|
|
1268
1501
|
const out = [];
|
|
@@ -1472,7 +1705,7 @@ const FONT_SIZE_FACTORS = {
|
|
|
1472
1705
|
xl: 1.429,
|
|
1473
1706
|
"2xl": 1.714
|
|
1474
1707
|
};
|
|
1475
|
-
/** 11.
|
|
1708
|
+
/** 11. 排版原子:字体族来自平台 Seed;字重是机械标准常量;字号按既定倍数派生。 */
|
|
1476
1709
|
function buildTypographyRecipes() {
|
|
1477
1710
|
const out = [];
|
|
1478
1711
|
for (const key of FONT_FAMILY_KEYS) out.push(ref([
|
|
@@ -1483,17 +1716,20 @@ function buildTypographyRecipes() {
|
|
|
1483
1716
|
kind: "fontStack",
|
|
1484
1717
|
seedRef: `typography.fontFamily.${key}`
|
|
1485
1718
|
}));
|
|
1486
|
-
for (const key of [
|
|
1487
|
-
"regular",
|
|
1488
|
-
"medium",
|
|
1489
|
-
"semibold"
|
|
1719
|
+
for (const [key, value] of [
|
|
1720
|
+
["regular", 400],
|
|
1721
|
+
["medium", 500],
|
|
1722
|
+
["semibold", 600]
|
|
1490
1723
|
]) out.push(ref([
|
|
1491
1724
|
"typography",
|
|
1492
1725
|
"fontWeight",
|
|
1493
1726
|
key
|
|
1494
1727
|
], {
|
|
1495
|
-
kind: "
|
|
1496
|
-
|
|
1728
|
+
kind: "literal",
|
|
1729
|
+
value
|
|
1730
|
+
}, {
|
|
1731
|
+
source: "CSS Fonts numeric font-weight scale",
|
|
1732
|
+
rationale: "Mechanical weight step; changing it is a semantic typography decision, not a global generator input."
|
|
1497
1733
|
}));
|
|
1498
1734
|
out.push(ref([
|
|
1499
1735
|
"typography",
|
|
@@ -1588,6 +1824,7 @@ function buildShadowRecipes() {
|
|
|
1588
1824
|
function allReferenceRecipes() {
|
|
1589
1825
|
return [
|
|
1590
1826
|
...buildColorRampRecipes(),
|
|
1827
|
+
...buildColorExtraRecipes(),
|
|
1591
1828
|
...buildSpacingRecipes(),
|
|
1592
1829
|
...buildRadiusRecipes(),
|
|
1593
1830
|
...buildControlIconSizeRecipes(),
|
|
@@ -1716,15 +1953,22 @@ const TYPOGRAPHY_ROLES = {
|
|
|
1716
1953
|
letterSpacingRef: T_LETTER
|
|
1717
1954
|
}
|
|
1718
1955
|
};
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1956
|
+
/**
|
|
1957
|
+
* 颜色角色的 Semantic 变量路径:绝大多数角色落在 `--qw-sem-color-*` 下;
|
|
1958
|
+
* `effect.shadow.*.color` 是复合阴影的颜色分量,对应 Figma 变量
|
|
1959
|
+
* `--qw-sem-effect-shadow-*-color`,不带 `color-` 前缀。
|
|
1960
|
+
*/
|
|
1961
|
+
function colorRoleSemPath(role) {
|
|
1962
|
+
const segs = role.split(".");
|
|
1963
|
+
return segs[0] === "effect" ? segs : ["color", ...segs];
|
|
1964
|
+
}
|
|
1965
|
+
const COLOR_ROLES = { ...FIGMA_T3_COLOR_ROLES };
|
|
1966
|
+
/** 复合阴影角色:形状取 Reference effect.shadow.<level>,颜色取各自的 effect.shadow.<role>.color 分量。 */
|
|
1967
|
+
const SHADOW_COMPOSITE_ROLES = {
|
|
1968
|
+
card: "sm",
|
|
1969
|
+
floating: "md",
|
|
1970
|
+
dialog: "lg"
|
|
1725
1971
|
};
|
|
1726
|
-
/** 复合阴影角色:形状取 Reference effect.shadow.<level>,颜色取 color.shadow。 */
|
|
1727
|
-
const SHADOW_COMPOSITE_ROLES = { dialog: "lg" };
|
|
1728
1972
|
/**
|
|
1729
1973
|
* 全量 Semantic 角色的结构化血缘表——"角色 → 引用的 Reference 路径(含 light/dark 分支)"。
|
|
1730
1974
|
* 与 buildSemanticStructural/buildSemanticColor 的产出一一对应(顺序一致)。
|
|
@@ -1817,7 +2061,7 @@ function semanticRoleEntries() {
|
|
|
1817
2061
|
"standard"
|
|
1818
2062
|
], "motion.easing.standard");
|
|
1819
2063
|
for (const [path, refs] of Object.entries(COLOR_ROLES)) {
|
|
1820
|
-
const segs =
|
|
2064
|
+
const segs = colorRoleSemPath(path);
|
|
1821
2065
|
out.push({
|
|
1822
2066
|
kind: "color",
|
|
1823
2067
|
path: segs.join("."),
|
|
@@ -1836,7 +2080,7 @@ function semanticRoleEntries() {
|
|
|
1836
2080
|
path: segs.join("."),
|
|
1837
2081
|
cssVar: semVar(segs),
|
|
1838
2082
|
shapeRef: `effect.shadow.${level}`,
|
|
1839
|
-
colorRole:
|
|
2083
|
+
colorRole: `effect.shadow.${role}.color`
|
|
1840
2084
|
});
|
|
1841
2085
|
}
|
|
1842
2086
|
return out;
|
|
@@ -1864,9 +2108,9 @@ function buildSemanticColor(reference, mode) {
|
|
|
1864
2108
|
if (entry.kind !== "color") continue;
|
|
1865
2109
|
out[entry.cssVar] = pick(tokens, entry.refPath[mode]);
|
|
1866
2110
|
}
|
|
1867
|
-
const shadowColor = out[semVar(["color", "shadow"])];
|
|
1868
2111
|
for (const [role, level] of Object.entries(SHADOW_COMPOSITE_ROLES)) {
|
|
1869
2112
|
const shape = shapes.shadow[level];
|
|
2113
|
+
const shadowColor = out[semVar(colorRoleSemPath(`effect.shadow.${role}.color`))];
|
|
1870
2114
|
out[semVar([
|
|
1871
2115
|
"effect",
|
|
1872
2116
|
"shadow",
|
|
@@ -1905,8 +2149,9 @@ function derive(seed) {
|
|
|
1905
2149
|
*
|
|
1906
2150
|
* 与静态 tokens.css(解析后的字面值,SSR 稳定出口)不同,live CSS 把整棵派生
|
|
1907
2151
|
* 树翻译成 CSS 原生表达式:
|
|
1908
|
-
* - T1 Seed 落为 `--qw-seed-*`
|
|
1909
|
-
* - T2 Reference
|
|
2152
|
+
* - T1 Seed 落为 `--qw-seed-*` 变量,承载需要全局级联的生成参数;
|
|
2153
|
+
* - T2 Reference 的 seed-derived 项用 relative color / calc() 派生,
|
|
2154
|
+
* design-calibrated / standard-constant 项保留 canonical 直接值;
|
|
1910
2155
|
* - T3 Semantic 全部是 `var(--qw-ref-*)` 别名;dark 模式只在暗色选择器下
|
|
1911
2156
|
* 重映射 sem → 不同的 ref 档位。
|
|
1912
2157
|
*
|
|
@@ -1914,11 +2159,11 @@ function derive(seed) {
|
|
|
1914
2159
|
* 级联更新,零 JS 重算。依赖现代 CSS(relative color / round()),刻意不做
|
|
1915
2160
|
* 老浏览器兼容——组件库消费方继续用静态 tokens.css。
|
|
1916
2161
|
*/
|
|
1917
|
-
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
2162
|
+
const round4$1 = (n) => Math.round(n * 1e4) / 1e4;
|
|
1918
2163
|
function seedVar(dotted) {
|
|
1919
2164
|
return toVar("seed", dotted.split("."));
|
|
1920
2165
|
}
|
|
1921
|
-
/** Seed 拍平成 `--qw-seed-*` → CSS
|
|
2166
|
+
/** Seed 拍平成 `--qw-seed-*` → CSS 值;T2 仍可包含校准值或标准常量。 */
|
|
1922
2167
|
function seedCssVars(seed) {
|
|
1923
2168
|
const out = {};
|
|
1924
2169
|
for (const [name, value] of Object.entries(seed.color)) out[seedVar(`color.${name}`)] = seedColorToCss(value);
|
|
@@ -1932,9 +2177,6 @@ function seedCssVars(seed) {
|
|
|
1932
2177
|
out[seedVar("size.opacity.alphaStep")] = String(seed.size.opacity.alphaStep);
|
|
1933
2178
|
out[seedVar("size.zIndex.step")] = String(seed.size.zIndex.step);
|
|
1934
2179
|
for (const key of FONT_FAMILY_KEYS) out[seedVar(`typography.fontFamily.${key}`)] = formatFontFamily(seed.typography.fontFamily[key]);
|
|
1935
|
-
out[seedVar("typography.fontWeight.regular")] = String(seed.typography.fontWeight.regular);
|
|
1936
|
-
out[seedVar("typography.fontWeight.medium")] = String(seed.typography.fontWeight.medium);
|
|
1937
|
-
out[seedVar("typography.fontWeight.semibold")] = String(seed.typography.fontWeight.semibold);
|
|
1938
2180
|
out[seedVar("typography.fontSize.base")] = seed.typography.fontSize.base;
|
|
1939
2181
|
out[seedVar("typography.lineHeight.base")] = String(seed.typography.lineHeight.base);
|
|
1940
2182
|
out[seedVar("typography.letterSpacing.base")] = seed.typography.letterSpacing.base;
|
|
@@ -1972,13 +2214,14 @@ function derivationToCssExpr(d) {
|
|
|
1972
2214
|
case "offsetSteps": return `calc(var(${seedVar(d.seedRef)}) ${d.n >= 0 ? "+" : "-"} var(${seedVar(d.stepRef)}) * ${Math.abs(d.n)})`;
|
|
1973
2215
|
case "oklchRamp": {
|
|
1974
2216
|
const l = L_TARGETS[d.step];
|
|
1975
|
-
const f = round4(chromaFactor(l));
|
|
2217
|
+
const f = round4$1(chromaFactor(l));
|
|
1976
2218
|
return `oklch(from var(${seedVar(d.seedRef)}) ${l} calc(c * ${f}) h)`;
|
|
1977
2219
|
}
|
|
1978
2220
|
case "figmaOklchScale": return d.value;
|
|
1979
|
-
case "seedAlpha": return `rgb(from var(${seedVar(d.seedRef)}) r g b / ${round4(d.alpha)})`;
|
|
2221
|
+
case "seedAlpha": return `rgb(from var(${seedVar(d.seedRef)}) r g b / ${round4$1(d.alpha)})`;
|
|
2222
|
+
case "stepAlpha": return stepAlphaCss(seedKeyOf(d.seedRef), d.step, d.alpha);
|
|
1980
2223
|
case "alphaScale": return `rgb(${d.channel === "black" ? "0 0 0" : "255 255 255"} / calc(var(${seedVar(d.stepRef)}) * ${d.multiplier}))`;
|
|
1981
|
-
case "literal": return d.value;
|
|
2224
|
+
case "literal": return String(d.value);
|
|
1982
2225
|
case "shadowLayer": {
|
|
1983
2226
|
const s = shadowShapeExprs(d.level);
|
|
1984
2227
|
const alpha = `calc(var(${seedVar("size.opacity.alphaStep")}) * ${SHADOW_LEVELS[d.level].alphaMult})`;
|
|
@@ -2021,6 +2264,372 @@ function buildLiveCss(seed, options = {}) {
|
|
|
2021
2264
|
].join("\n");
|
|
2022
2265
|
}
|
|
2023
2266
|
//#endregion
|
|
2267
|
+
//#region src/figma/color-snapshot.ts
|
|
2268
|
+
/**
|
|
2269
|
+
* color-snapshot — 生成 Figma `Color` 变量集合的 DTCG 快照(design/color.tokens.json)。
|
|
2270
|
+
*
|
|
2271
|
+
* Figma 导出自动化通道尚未建立(变量 ID 本次不回填),快照由
|
|
2272
|
+
* color-architecture.ts 的同一份事实表投影生成,供 validate:figma-color 与
|
|
2273
|
+
* color-contract 测试消费;接上 Figma 导出后以导出物替换本生成物。
|
|
2274
|
+
*/
|
|
2275
|
+
const NS$1 = "com.qianwen.qw-ui";
|
|
2276
|
+
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
2277
|
+
function hexComponents(hex) {
|
|
2278
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex);
|
|
2279
|
+
if (!m) throw new Error(`[qw] invalid hex color: ${hex}`);
|
|
2280
|
+
const n = Number.parseInt(m[1], 16);
|
|
2281
|
+
return [
|
|
2282
|
+
round4((n >> 16 & 255) / 255),
|
|
2283
|
+
round4((n >> 8 & 255) / 255),
|
|
2284
|
+
round4((n & 255) / 255)
|
|
2285
|
+
];
|
|
2286
|
+
}
|
|
2287
|
+
function colorValue(hex, alpha = 1) {
|
|
2288
|
+
return {
|
|
2289
|
+
colorSpace: "srgb",
|
|
2290
|
+
components: hexComponents(hex),
|
|
2291
|
+
alpha
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
function t1Ref(seed) {
|
|
2295
|
+
return `{qw.t1.${seed}}`;
|
|
2296
|
+
}
|
|
2297
|
+
/** T3 角色引用的 T2 点分路径("color.blue.600-a20" → "qw.t2.blue.600-a20")。 */
|
|
2298
|
+
function t2Ref(dotted) {
|
|
2299
|
+
const [, seed, step] = dotted.split(".");
|
|
2300
|
+
return `{qw.t2.${seed}.${step}}`;
|
|
2301
|
+
}
|
|
2302
|
+
function t2Token(value, provenance) {
|
|
2303
|
+
return {
|
|
2304
|
+
$type: "color",
|
|
2305
|
+
$value: value,
|
|
2306
|
+
$extensions: { [NS$1]: { provenance } }
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
function buildT1() {
|
|
2310
|
+
const t1 = {};
|
|
2311
|
+
for (const seed of T1_COLOR_KEYS) t1[seed] = {
|
|
2312
|
+
$type: "color",
|
|
2313
|
+
$value: colorValue(FIGMA_T1_SEED_HEX[seed])
|
|
2314
|
+
};
|
|
2315
|
+
return t1;
|
|
2316
|
+
}
|
|
2317
|
+
function buildT2() {
|
|
2318
|
+
const t2 = {};
|
|
2319
|
+
for (const seed of T1_COLOR_KEYS) {
|
|
2320
|
+
const seedHex = FIGMA_T1_SEED_HEX[seed];
|
|
2321
|
+
const steps = {};
|
|
2322
|
+
steps.base = t2Token(colorValue(seedHex), {
|
|
2323
|
+
sourceKind: "seed-alias",
|
|
2324
|
+
seedRefs: [t1Ref(seed)],
|
|
2325
|
+
formula: `= qw.t1.${seed}`
|
|
2326
|
+
});
|
|
2327
|
+
for (const alpha of T2_ALPHA_STEPS) steps[`a${alpha}`] = t2Token(colorValue(seedHex, alpha / 100), {
|
|
2328
|
+
sourceKind: "seed-derived",
|
|
2329
|
+
seedRefs: [t1Ref(seed)],
|
|
2330
|
+
formula: `= qw.t1.${seed} / ${alpha}%`
|
|
2331
|
+
});
|
|
2332
|
+
for (const step of T2_OKLCH_STEPS) steps[String(step)] = t2Token(colorValue(FIGMA_T2_SOLID_SCALE[seed][step]), {
|
|
2333
|
+
sourceKind: "design-calibrated",
|
|
2334
|
+
seedRefs: [t1Ref(seed)],
|
|
2335
|
+
source: `Figma Color/qw/t2/${seed}/${step}`,
|
|
2336
|
+
formula: `= figma Color/qw/t2/${seed}/${step}(OKLCH 设计校准)`
|
|
2337
|
+
});
|
|
2338
|
+
for (const extra of FIGMA_T2_STEP_ALPHA) {
|
|
2339
|
+
if (extra.seed !== seed) continue;
|
|
2340
|
+
steps[`${extra.step}-a${extra.alpha}`] = t2Token(colorValue(FIGMA_T2_SOLID_SCALE[seed][extra.step], extra.alpha / 100), {
|
|
2341
|
+
sourceKind: "design-calibrated",
|
|
2342
|
+
seedRefs: [t1Ref(seed)],
|
|
2343
|
+
source: `Figma Color/qw/t2/${seed}/${extra.step}-a${extra.alpha}`,
|
|
2344
|
+
formula: `= figma Color/qw/t2/${seed}/${extra.step}-a${extra.alpha}(步进锚定 Alpha)`
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
if (T2_PURE_SEEDS.includes(seed)) steps.pure = t2Token(colorValue("#000000"), {
|
|
2348
|
+
sourceKind: "standard-constant",
|
|
2349
|
+
seedRefs: [],
|
|
2350
|
+
source: "CSS Color sRGB endpoint",
|
|
2351
|
+
rationale: "Mathematical black endpoint for OLED canvas; no theme-level generator input.",
|
|
2352
|
+
formula: "= #000000(canonical 标准常量)"
|
|
2353
|
+
});
|
|
2354
|
+
t2[seed] = steps;
|
|
2355
|
+
}
|
|
2356
|
+
return t2;
|
|
2357
|
+
}
|
|
2358
|
+
function buildT3() {
|
|
2359
|
+
const t3 = {};
|
|
2360
|
+
for (const [role, refs] of Object.entries(FIGMA_T3_COLOR_ROLES)) {
|
|
2361
|
+
const segs = role.split(".");
|
|
2362
|
+
const node = {
|
|
2363
|
+
$type: "color",
|
|
2364
|
+
$value: t2Ref(refs.light),
|
|
2365
|
+
$extensions: { [NS$1]: {
|
|
2366
|
+
cssVar: toVar("sem", colorRoleSemPath(role)),
|
|
2367
|
+
modes: {
|
|
2368
|
+
light: t2Ref(refs.light),
|
|
2369
|
+
dark: t2Ref(refs.dark)
|
|
2370
|
+
}
|
|
2371
|
+
} }
|
|
2372
|
+
};
|
|
2373
|
+
let cur = t3;
|
|
2374
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
2375
|
+
const seg = segs[i];
|
|
2376
|
+
if (!(seg in cur)) cur[seg] = {};
|
|
2377
|
+
cur = cur[seg];
|
|
2378
|
+
}
|
|
2379
|
+
cur[segs[segs.length - 1]] = node;
|
|
2380
|
+
}
|
|
2381
|
+
return t3;
|
|
2382
|
+
}
|
|
2383
|
+
/** 生成 design/color.tokens.json 的完整 JSON 对象(qw/t1/t2/t3 结构)。 */
|
|
2384
|
+
function buildFigmaColorSnapshot() {
|
|
2385
|
+
return {
|
|
2386
|
+
$schema: "https://www.designtokens.org/schemas/2025.10/format.json",
|
|
2387
|
+
$description: "qw-ui Color 变量集合快照(Figma 节点 22258:49986)。Figma 导出自动化通道建立前,由 scripts/export-design-tokens.ts 从 color-architecture.ts 同一份事实表投影生成,请勿手改;接上 Figma 导出后以导出物替换。校验:pnpm --filter @qw-ui/tokens validate:figma-color。",
|
|
2388
|
+
$extensions: { [NS$1]: {
|
|
2389
|
+
collection: "Color",
|
|
2390
|
+
figmaNode: "22258:49986",
|
|
2391
|
+
generated: true,
|
|
2392
|
+
owner: "design-system"
|
|
2393
|
+
} },
|
|
2394
|
+
qw: {
|
|
2395
|
+
t1: buildT1(),
|
|
2396
|
+
t2: buildT2(),
|
|
2397
|
+
t3: buildT3()
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
//#endregion
|
|
2402
|
+
//#region src/figma/color-contract.ts
|
|
2403
|
+
const T2_OKLCH_STEP_KEYS = T2_OKLCH_STEPS.map(String);
|
|
2404
|
+
const NS = "com.qianwen.qw-ui";
|
|
2405
|
+
const T1_SET = new Set(T1_COLOR_KEYS);
|
|
2406
|
+
const T2_ALPHA_SET = new Set(T2_ALPHA_KEYS);
|
|
2407
|
+
const T2_OKLCH_SET = new Set(T2_OKLCH_STEP_KEYS);
|
|
2408
|
+
const T2_PURE_SEED_SET = new Set(T2_PURE_SEEDS);
|
|
2409
|
+
const T3_SEGMENT = /^[a-z]+(?:-[a-z]+)*$/;
|
|
2410
|
+
/** 步进锚定 Alpha 步名(如 "500-a20"):OKLCH 档位 + Alpha 档位两段。 */
|
|
2411
|
+
const T2_STEP_ALPHA_RE = /^(\d+)-a(\d+)$/;
|
|
2412
|
+
const PROVENANCE_SOURCE_KINDS = /* @__PURE__ */ new Set([
|
|
2413
|
+
"seed-alias",
|
|
2414
|
+
"seed-derived",
|
|
2415
|
+
"design-calibrated",
|
|
2416
|
+
"standard-constant",
|
|
2417
|
+
"external-standard"
|
|
2418
|
+
]);
|
|
2419
|
+
const SEED_LINEAGE_KINDS = /* @__PURE__ */ new Set([
|
|
2420
|
+
"seed-alias",
|
|
2421
|
+
"seed-derived",
|
|
2422
|
+
"design-calibrated"
|
|
2423
|
+
]);
|
|
2424
|
+
function isObject(value) {
|
|
2425
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2426
|
+
}
|
|
2427
|
+
function collectLeaves(node, path = [], out = []) {
|
|
2428
|
+
if (!isObject(node)) return out;
|
|
2429
|
+
if ("$value" in node) {
|
|
2430
|
+
out.push({
|
|
2431
|
+
path,
|
|
2432
|
+
value: node.$value,
|
|
2433
|
+
extensions: isObject(node.$extensions) ? node.$extensions : {}
|
|
2434
|
+
});
|
|
2435
|
+
return out;
|
|
2436
|
+
}
|
|
2437
|
+
for (const [key, child] of Object.entries(node)) {
|
|
2438
|
+
if (key.startsWith("$")) continue;
|
|
2439
|
+
collectLeaves(child, [...path, key], out);
|
|
2440
|
+
}
|
|
2441
|
+
return out;
|
|
2442
|
+
}
|
|
2443
|
+
function qwPath(path) {
|
|
2444
|
+
const index = path.indexOf("qw");
|
|
2445
|
+
return index >= 0 ? path.slice(index) : null;
|
|
2446
|
+
}
|
|
2447
|
+
function pathKey(path) {
|
|
2448
|
+
return path.join(".");
|
|
2449
|
+
}
|
|
2450
|
+
function refToPath(ref) {
|
|
2451
|
+
if (typeof ref !== "string") return null;
|
|
2452
|
+
const match = /^\{(.+)\}$/.exec(ref.trim());
|
|
2453
|
+
if (!match) return null;
|
|
2454
|
+
return match[1].split(/[./]/).filter(Boolean);
|
|
2455
|
+
}
|
|
2456
|
+
function extensionObject(leaf) {
|
|
2457
|
+
const ext = leaf.extensions[NS];
|
|
2458
|
+
return isObject(ext) ? ext : {};
|
|
2459
|
+
}
|
|
2460
|
+
function modesOf(leaf) {
|
|
2461
|
+
const ext = extensionObject(leaf);
|
|
2462
|
+
return isObject(ext.modes) ? ext.modes : {};
|
|
2463
|
+
}
|
|
2464
|
+
function provenanceOf(leaf) {
|
|
2465
|
+
const ext = extensionObject(leaf);
|
|
2466
|
+
return isObject(ext.provenance) ? ext.provenance : {};
|
|
2467
|
+
}
|
|
2468
|
+
function isSolidColorValue(value) {
|
|
2469
|
+
if (!isObject(value)) return false;
|
|
2470
|
+
if (value.colorSpace !== "srgb" && value.colorSpace !== "oklch") return false;
|
|
2471
|
+
if (!Array.isArray(value.components) || value.components.length !== 3) return false;
|
|
2472
|
+
return value.alpha === 1;
|
|
2473
|
+
}
|
|
2474
|
+
function isColorLikeValue(value) {
|
|
2475
|
+
if (!isObject(value)) return false;
|
|
2476
|
+
if (value.colorSpace !== "srgb" && value.colorSpace !== "oklch") return false;
|
|
2477
|
+
if (!Array.isArray(value.components) || value.components.length !== 3) return false;
|
|
2478
|
+
return typeof value.alpha === "number" && value.alpha >= 0 && value.alpha <= 1;
|
|
2479
|
+
}
|
|
2480
|
+
function validateNoUnexpectedName(path, errors) {
|
|
2481
|
+
const joined = pathKey(path);
|
|
2482
|
+
if (joined.includes("_")) errors.push(`${joined}: token name must not contain underscores`);
|
|
2483
|
+
if (path.some((segment) => /[A-Z]/.test(segment))) errors.push(`${joined}: token name must be lowercase`);
|
|
2484
|
+
}
|
|
2485
|
+
function validateT3Path(path, errors) {
|
|
2486
|
+
const joined = pathKey(path);
|
|
2487
|
+
for (const segment of path.slice(2)) if (!T3_SEGMENT.test(segment)) errors.push(`${joined}: T3 semantic segment "${segment}" must use kebab-case words without numeric suffixes`);
|
|
2488
|
+
}
|
|
2489
|
+
/** T2 步名分类:base / alpha / oklch / step-alpha / pure。 */
|
|
2490
|
+
function classifyT2Step(step) {
|
|
2491
|
+
if (step === "base") return "base";
|
|
2492
|
+
if (T2_ALPHA_SET.has(step)) return "alpha";
|
|
2493
|
+
if (T2_OKLCH_SET.has(step)) return "oklch";
|
|
2494
|
+
if (step === "pure") return "pure";
|
|
2495
|
+
const match = T2_STEP_ALPHA_RE.exec(step);
|
|
2496
|
+
if (match && T2_OKLCH_SET.has(match[1]) && T2_ALPHA_SET.has(`a${match[2]}`)) return "step-alpha";
|
|
2497
|
+
return null;
|
|
2498
|
+
}
|
|
2499
|
+
/** 各 T2 步类对应的 provenance.sourceKind(血缘治理不变量)。 */
|
|
2500
|
+
const T2_STEP_SOURCE_KIND = {
|
|
2501
|
+
base: "seed-alias",
|
|
2502
|
+
alpha: "seed-derived",
|
|
2503
|
+
oklch: "design-calibrated",
|
|
2504
|
+
"step-alpha": "design-calibrated",
|
|
2505
|
+
pure: "standard-constant"
|
|
2506
|
+
};
|
|
2507
|
+
function validateT2Provenance(leaf, seed, step, errors) {
|
|
2508
|
+
const joined = pathKey(leaf.path);
|
|
2509
|
+
const provenance = provenanceOf(leaf);
|
|
2510
|
+
if (Object.keys(provenance).length === 0) {
|
|
2511
|
+
errors.push(`${joined}: T2 token must declare $extensions.${NS}.provenance`);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
const { sourceKind, seedRefs } = provenance;
|
|
2515
|
+
if (typeof sourceKind !== "string" || !PROVENANCE_SOURCE_KINDS.has(sourceKind)) {
|
|
2516
|
+
errors.push(`${joined}: provenance.sourceKind must be one of ${[...PROVENANCE_SOURCE_KINDS].join(", ")}`);
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
const stepKind = classifyT2Step(step);
|
|
2520
|
+
if (stepKind && T2_STEP_SOURCE_KIND[stepKind] !== sourceKind) errors.push(`${joined}: T2 ${stepKind} token must declare provenance.sourceKind = "${T2_STEP_SOURCE_KIND[stepKind]}"`);
|
|
2521
|
+
if (SEED_LINEAGE_KINDS.has(sourceKind)) {
|
|
2522
|
+
const expected = `{qw.t1.${seed}}`;
|
|
2523
|
+
if (!Array.isArray(seedRefs) || seedRefs.length !== 1 || seedRefs[0] !== expected) errors.push(`${joined}: provenance.seedRefs must be exactly ["${expected}"]`);
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
if (Array.isArray(seedRefs) && seedRefs.length > 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must not fabricate Seed lineage`);
|
|
2527
|
+
if (typeof provenance.source !== "string" || provenance.source.length === 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must declare provenance.source`);
|
|
2528
|
+
if (typeof provenance.rationale !== "string" || provenance.rationale.length === 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must declare provenance.rationale`);
|
|
2529
|
+
}
|
|
2530
|
+
/** T3 角色全集(qw 路径),由 FIGMA_T3_COLOR_ROLES 投影。 */
|
|
2531
|
+
function figmaT3RolePaths() {
|
|
2532
|
+
return Object.keys(FIGMA_T3_COLOR_ROLES).map((role) => pathKey([
|
|
2533
|
+
"qw",
|
|
2534
|
+
"t3",
|
|
2535
|
+
...role.split(".")
|
|
2536
|
+
]));
|
|
2537
|
+
}
|
|
2538
|
+
function validateFigmaColorTokens(doc) {
|
|
2539
|
+
const errors = [];
|
|
2540
|
+
const leaves = collectLeaves(doc).map((leaf) => ({
|
|
2541
|
+
...leaf,
|
|
2542
|
+
qwPath: qwPath(leaf.path)
|
|
2543
|
+
})).filter((leaf) => leaf.qwPath != null);
|
|
2544
|
+
const byQwPath = /* @__PURE__ */ new Map();
|
|
2545
|
+
for (const leaf of leaves) {
|
|
2546
|
+
const key = pathKey(leaf.qwPath);
|
|
2547
|
+
if (byQwPath.has(key)) errors.push(`${key}: duplicated token path`);
|
|
2548
|
+
byQwPath.set(key, leaf);
|
|
2549
|
+
validateNoUnexpectedName(leaf.qwPath, errors);
|
|
2550
|
+
}
|
|
2551
|
+
const t1 = leaves.filter((leaf) => leaf.qwPath[1] === "t1");
|
|
2552
|
+
const t2 = leaves.filter((leaf) => leaf.qwPath[1] === "t2");
|
|
2553
|
+
const t3 = leaves.filter((leaf) => leaf.qwPath[1] === "t3");
|
|
2554
|
+
const unknownLayers = leaves.filter((leaf) => ![
|
|
2555
|
+
"t1",
|
|
2556
|
+
"t2",
|
|
2557
|
+
"t3"
|
|
2558
|
+
].includes(leaf.qwPath[1] ?? ""));
|
|
2559
|
+
for (const leaf of unknownLayers) errors.push(`${pathKey(leaf.qwPath)}: unknown Color token layer`);
|
|
2560
|
+
const t1Keys = /* @__PURE__ */ new Set();
|
|
2561
|
+
for (const leaf of t1) {
|
|
2562
|
+
const [, , key, ...rest] = leaf.qwPath;
|
|
2563
|
+
const joined = pathKey(leaf.qwPath);
|
|
2564
|
+
if (rest.length > 0 || !key) errors.push(`${joined}: T1 token must be qw/t1/{seed}`);
|
|
2565
|
+
if (key && !T1_SET.has(key)) errors.push(`${joined}: unexpected T1 seed "${key}"`);
|
|
2566
|
+
if (!isSolidColorValue(leaf.value)) errors.push(`${joined}: T1 seed must be a solid color with alpha = 1`);
|
|
2567
|
+
if (key) t1Keys.add(key);
|
|
2568
|
+
}
|
|
2569
|
+
const missingT1 = T1_COLOR_KEYS.filter((key) => !t1Keys.has(key));
|
|
2570
|
+
const extraT1 = [...t1Keys].filter((key) => !T1_SET.has(key));
|
|
2571
|
+
if (missingT1.length) errors.push(`qw.t1: missing required seeds ${missingT1.join(", ")}`);
|
|
2572
|
+
if (extraT1.length) errors.push(`qw.t1: unexpected seeds ${extraT1.join(", ")}`);
|
|
2573
|
+
const t2BySeed = /* @__PURE__ */ new Map();
|
|
2574
|
+
for (const seed of T1_COLOR_KEYS) t2BySeed.set(seed, /* @__PURE__ */ new Set());
|
|
2575
|
+
for (const leaf of t2) {
|
|
2576
|
+
const [, , seed, step, ...rest] = leaf.qwPath;
|
|
2577
|
+
const joined = pathKey(leaf.qwPath);
|
|
2578
|
+
if (rest.length > 0 || !seed || !step) errors.push(`${joined}: T2 token must be qw/t2/{seed}/{base|alpha|oklch-step}`);
|
|
2579
|
+
if (seed && !T1_SET.has(seed)) errors.push(`${joined}: T2 seed "${seed}" must exist in T1`);
|
|
2580
|
+
const stepKind = step ? classifyT2Step(step) : null;
|
|
2581
|
+
if (step && !stepKind) errors.push(`${joined}: unsupported T2 step "${step}"`);
|
|
2582
|
+
if (stepKind === "pure" && seed && !T2_PURE_SEED_SET.has(seed)) errors.push(`${joined}: "pure" step is only provided for seeds ${[...T2_PURE_SEED_SET].join(", ")}`);
|
|
2583
|
+
if (!isColorLikeValue(leaf.value) && step !== "base") errors.push(`${joined}: T2 token must carry a concrete color value`);
|
|
2584
|
+
if (seed && step && T1_SET.has(seed) && stepKind) {
|
|
2585
|
+
t2BySeed.get(seed).add(step);
|
|
2586
|
+
validateT2Provenance(leaf, seed, step, errors);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
for (const seed of T1_COLOR_KEYS) {
|
|
2590
|
+
const steps = t2BySeed.get(seed);
|
|
2591
|
+
const missingAlpha = T2_ALPHA_KEYS.filter((step) => !steps.has(step));
|
|
2592
|
+
const missingOklch = T2_OKLCH_STEP_KEYS.filter((step) => !steps.has(step));
|
|
2593
|
+
const unexpected = [...steps].filter((step) => !classifyT2Step(step));
|
|
2594
|
+
if (!steps.has("base")) errors.push(`qw.t2.${seed}: missing base token`);
|
|
2595
|
+
if (missingAlpha.length) errors.push(`qw.t2.${seed}: missing alpha steps ${missingAlpha.join(", ")}`);
|
|
2596
|
+
if (missingOklch.length) errors.push(`qw.t2.${seed}: missing OKLCH steps ${missingOklch.join(", ")}`);
|
|
2597
|
+
if (unexpected.length) errors.push(`qw.t2.${seed}: unexpected steps ${unexpected.join(", ")}`);
|
|
2598
|
+
}
|
|
2599
|
+
const expectedT3 = new Set(figmaT3RolePaths());
|
|
2600
|
+
const actualT3 = /* @__PURE__ */ new Set();
|
|
2601
|
+
for (const leaf of t3) {
|
|
2602
|
+
const joined = pathKey(leaf.qwPath);
|
|
2603
|
+
actualT3.add(joined);
|
|
2604
|
+
validateT3Path(leaf.qwPath, errors);
|
|
2605
|
+
if (!expectedT3.has(joined)) errors.push(`${joined}: unexpected T3 role (not in the 60-role Figma set)`);
|
|
2606
|
+
const modes = modesOf(leaf);
|
|
2607
|
+
const light = refToPath(modes.light);
|
|
2608
|
+
const dark = refToPath(modes.dark);
|
|
2609
|
+
if (!light || !dark) {
|
|
2610
|
+
errors.push(`${joined}: T3 token must declare both light and dark mode references`);
|
|
2611
|
+
continue;
|
|
2612
|
+
}
|
|
2613
|
+
for (const [modeName, ref] of [["light", light], ["dark", dark]]) if (ref[0] !== "qw" || ref[1] !== "t2") errors.push(`${joined}: ${modeName} mode must reference a T2 token`);
|
|
2614
|
+
else if (!byQwPath.has(pathKey(ref))) errors.push(`${joined}: ${modeName} mode references missing token {${pathKey(ref)}}`);
|
|
2615
|
+
}
|
|
2616
|
+
const missingT3 = [...expectedT3].filter((role) => !actualT3.has(role));
|
|
2617
|
+
if (missingT3.length) errors.push(`qw.t3: missing required roles ${missingT3.join(", ")}`);
|
|
2618
|
+
return {
|
|
2619
|
+
ok: errors.length === 0,
|
|
2620
|
+
errors,
|
|
2621
|
+
counts: {
|
|
2622
|
+
t1: t1.length,
|
|
2623
|
+
t2: t2.length,
|
|
2624
|
+
t3: t3.length
|
|
2625
|
+
}
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
function assertFigmaColorTokens(doc) {
|
|
2629
|
+
const result = validateFigmaColorTokens(doc);
|
|
2630
|
+
if (!result.ok) throw new Error(`Invalid Figma Color tokens:\n${result.errors.map((error) => `- ${error}`).join("\n")}`);
|
|
2631
|
+
}
|
|
2632
|
+
//#endregion
|
|
2024
2633
|
//#region src/compat/schema.ts
|
|
2025
2634
|
/** DTCG 兼容的颜色字面量:hex、hex/NN% 透明度后缀,或结构化 srgb。 */
|
|
2026
2635
|
const legacyHexColor = z.string().regex(/^#[0-9A-Fa-f]{6}(\/\d{1,3}%)?$/, "期望 #RRGGBB 或 #RRGGBB/NN%");
|
|
@@ -2227,6 +2836,6 @@ function formatCompatTypes(build) {
|
|
|
2227
2836
|
return lines.join("\n");
|
|
2228
2837
|
}
|
|
2229
2838
|
//#endregion
|
|
2230
|
-
export { COLOR_ROLES, FIGMA_COLOR_COLLECTION, FIGMA_COLOR_PREFIX, FIGMA_T1_SEED_HEX, FIGMA_T2_SOLID_SCALE, FIGMA_T3_COLOR_ROLES, L_TARGETS, RAMP_COLORS, SHADOW_COMPOSITE_ROLES, STEPS, SeedSchema, T1_COLOR_KEYS, T2_ALPHA_KEYS, T2_ALPHA_STEPS, T2_OKLCH_STEPS, TYPOGRAPHY_ROLES, allReferenceRecipes, buildCompatFromRegistry, buildLiveCss, buildReference, buildSemanticColor, buildSemanticStructural, compatPathToCssVar, compatRegistrySchema, contrastRatio, defaultSeed, derivationToCssExpr, derive, evaluateDerivation, formatCompatTypes, formatDerivation, hexToSeedColor, ramp, seedColorToCss, seedColorToHex, seedCssVars, seedRefsOf, seedValueAt, semanticRoleEntries, toVar, validateSeed };
|
|
2839
|
+
export { COLOR_ROLES, FIGMA_COLOR_COLLECTION, FIGMA_COLOR_PREFIX, FIGMA_T1_SEED_HEX, FIGMA_T2_SOLID_SCALE, FIGMA_T2_STEP_ALPHA, FIGMA_T3_COLOR_ROLES, L_TARGETS, RAMP_COLORS, SHADOW_COMPOSITE_ROLES, STEPS, SeedSchema, T1_COLOR_KEYS, T2_ALPHA_KEYS, T2_ALPHA_STEPS, T2_OKLCH_STEPS, T2_PURE_SEEDS, T2_PURE_STEP, TYPOGRAPHY_ROLES, allReferenceRecipes, assertFigmaColorTokens, buildCompatFromRegistry, buildFigmaColorSnapshot, buildLiveCss, buildReference, buildSemanticColor, buildSemanticStructural, compatPathToCssVar, compatRegistrySchema, contrastRatio, defaultSeed, derivationToCssExpr, derive, evaluateDerivation, figmaT3RolePaths, formatCompatTypes, formatDerivation, hexToSeedColor, ramp, seedColorToCss, seedColorToHex, seedCssVars, seedRefsOf, seedValueAt, semanticRoleEntries, sourceKindOf, toVar, validateFigmaColorTokens, validateSeed };
|
|
2231
2840
|
|
|
2232
2841
|
//# sourceMappingURL=index.js.map
|