@qw-ui/tokens 0.3.0-next.4 → 0.3.0-next.40
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 +8 -5
- package/dist/color-architecture.d.ts +139 -2
- package/dist/derive/index.d.ts +2 -2
- package/dist/derive/live-css.d.ts +1 -1
- package/dist/derive/reference.d.ts +62 -13
- package/dist/derive/semantic.d.ts +17 -1
- package/dist/derive/tailwind.d.ts +31 -0
- 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 +5 -4
- package/dist/index.js +759 -240
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +3 -3
- package/dist/tailwind.css +79 -0
- package/dist/tokens.css +257 -218
- package/dist/tokens.json +196 -93
- package/dist/tokens.live.css +127 -9
- package/dist/tokens.ts +198 -130
- package/package.json +2 -1
- package/dist/compat/build.d.ts +0 -14
- package/dist/compat/color.d.ts +0 -8
- package/dist/compat/index.d.ts +0 -5
- package/dist/compat/schema.d.ts +0 -114
package/dist/index.js
CHANGED
|
@@ -620,6 +620,11 @@ const FIGMA_T2_SOLID_SCALE = {
|
|
|
620
620
|
900: "#200B00"
|
|
621
621
|
}
|
|
622
622
|
};
|
|
623
|
+
/**
|
|
624
|
+
* Figma「Color Token System」T3 语义角色表(60 个,light/dark 均引用 T2 路径)。
|
|
625
|
+
* 与 Figma 节点 22258:49986 逐条对齐;`effect.shadow.*.color` 为复合阴影的颜色
|
|
626
|
+
* 分量(对应 Figma 变量 `--qw-sem-effect-shadow-*-color`)。
|
|
627
|
+
*/
|
|
623
628
|
const FIGMA_T3_COLOR_ROLES = {
|
|
624
629
|
"surface.base": {
|
|
625
630
|
light: "color.white.base",
|
|
@@ -641,6 +646,26 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
641
646
|
light: "color.blue.50",
|
|
642
647
|
dark: "color.blue.800"
|
|
643
648
|
},
|
|
649
|
+
"surface.canvas": {
|
|
650
|
+
light: "color.white.base",
|
|
651
|
+
dark: "color.black.900"
|
|
652
|
+
},
|
|
653
|
+
"surface.sunken": {
|
|
654
|
+
light: "color.gray.100",
|
|
655
|
+
dark: "color.gray.800"
|
|
656
|
+
},
|
|
657
|
+
"surface.selected": {
|
|
658
|
+
light: "color.black.a5",
|
|
659
|
+
dark: "color.white.a10"
|
|
660
|
+
},
|
|
661
|
+
"surface.inverse": {
|
|
662
|
+
light: "color.black.800",
|
|
663
|
+
dark: "color.black.800"
|
|
664
|
+
},
|
|
665
|
+
"surface.canvas-subtle": {
|
|
666
|
+
light: "color.gray.50",
|
|
667
|
+
dark: "color.black.900"
|
|
668
|
+
},
|
|
644
669
|
"content.primary": {
|
|
645
670
|
light: "color.black.base",
|
|
646
671
|
dark: "color.white.base"
|
|
@@ -661,6 +686,30 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
661
686
|
light: "color.gray.400",
|
|
662
687
|
dark: "color.gray.500"
|
|
663
688
|
},
|
|
689
|
+
"content.disabled": {
|
|
690
|
+
light: "color.black.a30",
|
|
691
|
+
dark: "color.white.a30"
|
|
692
|
+
},
|
|
693
|
+
"content.link": {
|
|
694
|
+
light: "color.blue.700",
|
|
695
|
+
dark: "color.blue.400"
|
|
696
|
+
},
|
|
697
|
+
"content.icon-subtle": {
|
|
698
|
+
light: "color.black.a5",
|
|
699
|
+
dark: "color.white.a10"
|
|
700
|
+
},
|
|
701
|
+
"content.inverse": {
|
|
702
|
+
light: "color.white.base",
|
|
703
|
+
dark: "color.black.base"
|
|
704
|
+
},
|
|
705
|
+
"content.on-solid": {
|
|
706
|
+
light: "color.white.base",
|
|
707
|
+
dark: "color.white.base"
|
|
708
|
+
},
|
|
709
|
+
"content.on-warning-subtle": {
|
|
710
|
+
light: "color.yellow.800",
|
|
711
|
+
dark: "color.yellow.200"
|
|
712
|
+
},
|
|
664
713
|
"border.default": {
|
|
665
714
|
light: "color.gray.100",
|
|
666
715
|
dark: "color.gray.700"
|
|
@@ -673,6 +722,18 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
673
722
|
light: "color.brand.base",
|
|
674
723
|
dark: "color.brand.500"
|
|
675
724
|
},
|
|
725
|
+
"border.subtle": {
|
|
726
|
+
light: "color.black.a5",
|
|
727
|
+
dark: "color.white.a5"
|
|
728
|
+
},
|
|
729
|
+
"border.disabled": {
|
|
730
|
+
light: "color.black.a10",
|
|
731
|
+
dark: "color.white.a10"
|
|
732
|
+
},
|
|
733
|
+
"border.on-media": {
|
|
734
|
+
light: "color.white.base",
|
|
735
|
+
dark: "color.white.base"
|
|
736
|
+
},
|
|
676
737
|
"fill.subtle": {
|
|
677
738
|
light: "color.gray.50",
|
|
678
739
|
dark: "color.white.a20"
|
|
@@ -689,6 +750,10 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
689
750
|
light: "color.gray.50",
|
|
690
751
|
dark: "color.gray.900"
|
|
691
752
|
},
|
|
753
|
+
"fill.track": {
|
|
754
|
+
light: "color.black.a10",
|
|
755
|
+
dark: "color.white.a20"
|
|
756
|
+
},
|
|
692
757
|
"action.brand": {
|
|
693
758
|
light: "color.brand.base",
|
|
694
759
|
dark: "color.brand.500"
|
|
@@ -705,6 +770,38 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
705
770
|
light: "color.white.base",
|
|
706
771
|
dark: "color.black.base"
|
|
707
772
|
},
|
|
773
|
+
"control-action.brand.subtle": {
|
|
774
|
+
light: "color.brand.a10",
|
|
775
|
+
dark: "color.brand.a20"
|
|
776
|
+
},
|
|
777
|
+
"control-action.product.subtle": {
|
|
778
|
+
light: "color.blue.a10",
|
|
779
|
+
dark: "color.blue.a20"
|
|
780
|
+
},
|
|
781
|
+
"control-action.danger.default": {
|
|
782
|
+
light: "color.red.400",
|
|
783
|
+
dark: "color.red.400"
|
|
784
|
+
},
|
|
785
|
+
"control-action.danger.subtle": {
|
|
786
|
+
light: "color.red.a10",
|
|
787
|
+
dark: "color.red.a20"
|
|
788
|
+
},
|
|
789
|
+
"control-action.neutral.subtle": {
|
|
790
|
+
light: "color.black.a5",
|
|
791
|
+
dark: "color.white.a10"
|
|
792
|
+
},
|
|
793
|
+
"control-selection.brand": {
|
|
794
|
+
light: "color.brand.a10",
|
|
795
|
+
dark: "color.brand.a20"
|
|
796
|
+
},
|
|
797
|
+
"control-selection.product": {
|
|
798
|
+
light: "color.blue.a10",
|
|
799
|
+
dark: "color.blue.a20"
|
|
800
|
+
},
|
|
801
|
+
"selection.text-background": {
|
|
802
|
+
light: "color.blue.a20",
|
|
803
|
+
dark: "color.blue.a40"
|
|
804
|
+
},
|
|
708
805
|
"status.success": {
|
|
709
806
|
light: "color.green.base",
|
|
710
807
|
dark: "color.green.base"
|
|
@@ -721,13 +818,53 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
721
818
|
light: "color.blue.base",
|
|
722
819
|
dark: "color.blue.600"
|
|
723
820
|
},
|
|
821
|
+
"status.success-subtle": {
|
|
822
|
+
light: "color.green.a10",
|
|
823
|
+
dark: "color.green.a20"
|
|
824
|
+
},
|
|
825
|
+
"status.warning-subtle": {
|
|
826
|
+
light: "color.yellow.a20",
|
|
827
|
+
dark: "color.yellow.a20"
|
|
828
|
+
},
|
|
829
|
+
"status.danger-subtle": {
|
|
830
|
+
light: "color.red.a10",
|
|
831
|
+
dark: "color.red.a20"
|
|
832
|
+
},
|
|
833
|
+
"status.info-subtle": {
|
|
834
|
+
light: "color.blue.a10",
|
|
835
|
+
dark: "color.blue.a20"
|
|
836
|
+
},
|
|
724
837
|
"overlay.scrim": {
|
|
725
838
|
light: "color.black.a50",
|
|
726
839
|
dark: "color.black.a70"
|
|
727
840
|
},
|
|
728
841
|
"overlay.guide": {
|
|
729
|
-
light: "color.
|
|
730
|
-
dark: "color.
|
|
842
|
+
light: "color.blue.a10",
|
|
843
|
+
dark: "color.blue.a20"
|
|
844
|
+
},
|
|
845
|
+
"overlay.media-subtle": {
|
|
846
|
+
light: "color.black.a40",
|
|
847
|
+
dark: "color.black.a40"
|
|
848
|
+
},
|
|
849
|
+
"overlay.media-strong": {
|
|
850
|
+
light: "color.black.a50",
|
|
851
|
+
dark: "color.black.a50"
|
|
852
|
+
},
|
|
853
|
+
shadow: {
|
|
854
|
+
light: "color.black.a10",
|
|
855
|
+
dark: "color.black.a50"
|
|
856
|
+
},
|
|
857
|
+
"effect.shadow.card.color": {
|
|
858
|
+
light: "color.black.a10",
|
|
859
|
+
dark: "color.black.a50"
|
|
860
|
+
},
|
|
861
|
+
"effect.shadow.floating.color": {
|
|
862
|
+
light: "color.black.a10",
|
|
863
|
+
dark: "color.black.a50"
|
|
864
|
+
},
|
|
865
|
+
"effect.shadow.dialog.color": {
|
|
866
|
+
light: "color.black.a10",
|
|
867
|
+
dark: "color.black.a50"
|
|
731
868
|
}
|
|
732
869
|
};
|
|
733
870
|
//#endregion
|
|
@@ -828,11 +965,11 @@ const cubicBezier = z.tuple([
|
|
|
828
965
|
const fontStack = z.array(z.string()).min(1);
|
|
829
966
|
const colorSeedShape = Object.fromEntries(T1_COLOR_KEYS.map((key) => [key, seedColor]));
|
|
830
967
|
/**
|
|
831
|
-
* SeedSchema — T1 Seed
|
|
968
|
+
* SeedSchema — T1 Seed 与兼容输入的运行时校验(RFC 0001 §2.1,v4 修订)。
|
|
832
969
|
*
|
|
833
970
|
* 五大类目 color/size/typography/motion/effect,字段结构对齐
|
|
834
|
-
* packages/tokens/design/seed.tokens.json
|
|
835
|
-
*
|
|
971
|
+
* packages/tokens/design/seed.tokens.json。部分已不再驱动 canonical T2 的字段暂时
|
|
972
|
+
* 保留为兼容输入;新 Reference 不得为了形式完整继续扩充伪 Seed。
|
|
836
973
|
*
|
|
837
974
|
* v3 变化:
|
|
838
975
|
* - 颜色全部结构化(DTCG Color module,支持 srgb/oklch)。
|
|
@@ -881,8 +1018,11 @@ base: z.string() }),
|
|
|
881
1018
|
textWindowsJa: fontStack
|
|
882
1019
|
}),
|
|
883
1020
|
fontWeight: z.object({
|
|
1021
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
884
1022
|
regular: z.number(),
|
|
1023
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
885
1024
|
medium: z.number(),
|
|
1025
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
886
1026
|
semibold: z.number()
|
|
887
1027
|
}),
|
|
888
1028
|
fontSize: z.object({ base: z.string() }),
|
|
@@ -1168,7 +1308,7 @@ function evaluateDerivation(d, seed) {
|
|
|
1168
1308
|
}
|
|
1169
1309
|
case "fontStack": return formatFontFamily(seedValueAt(seed, d.seedRef));
|
|
1170
1310
|
case "cubicBezier": return formatCubicBezier(seedValueAt(seed, d.seedRef));
|
|
1171
|
-
case "literal": return d.value;
|
|
1311
|
+
case "literal": return String(d.value);
|
|
1172
1312
|
case "shadowLayer": {
|
|
1173
1313
|
const { offsetX, offsetY, blur, spread } = shadowShapeOf(seed, d.level);
|
|
1174
1314
|
return `${offsetX} ${offsetY} ${blur} ${spread} ${rgba([
|
|
@@ -1197,6 +1337,17 @@ function seedRefsOf(d) {
|
|
|
1197
1337
|
case "shadowLayer": return ["effect.shadow.base", "size.opacity.alphaStep"];
|
|
1198
1338
|
}
|
|
1199
1339
|
}
|
|
1340
|
+
/** Derivation AST → 规范来源分类;作为 Figma/DTCG provenance 的唯一映射口。 */
|
|
1341
|
+
function sourceKindOf(d) {
|
|
1342
|
+
switch (d.kind) {
|
|
1343
|
+
case "alias":
|
|
1344
|
+
case "fontStack":
|
|
1345
|
+
case "cubicBezier": return "seed-alias";
|
|
1346
|
+
case "figmaOklchScale": return "design-calibrated";
|
|
1347
|
+
case "literal": return "standard-constant";
|
|
1348
|
+
default: return "seed-derived";
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1200
1351
|
/** 人读派生公式(Token Studio 展示用)。 */
|
|
1201
1352
|
function formatDerivation(d) {
|
|
1202
1353
|
const sign = (n) => n >= 0 ? "+" : "−";
|
|
@@ -1212,18 +1363,21 @@ function formatDerivation(d) {
|
|
|
1212
1363
|
case "figmaOklchScale": return `= figma Color/qw/t2/${d.seedRef.split(".")[1]}/${d.step}(OKLCH 设计校准)`;
|
|
1213
1364
|
case "seedAlpha": return `= seed.${d.seedRef} / ${round2(d.alpha * 100)}%`;
|
|
1214
1365
|
case "alphaScale": return `= ${d.channel === "black" ? "黑" : "白"} / seed.${d.stepRef} × ${d.multiplier}`;
|
|
1215
|
-
case "literal": return `= ${d.value}
|
|
1366
|
+
case "literal": return `= ${d.value}(canonical 标准常量)`;
|
|
1216
1367
|
case "shadowLayer": {
|
|
1217
1368
|
const f = SHADOW_LEVELS[d.level];
|
|
1218
1369
|
return `= seed.effect.shadow.base 形状 × {offset ${f.offset}, blur ${f.blur}} · 色 = 黑 / alphaStep × ${f.alphaMult}`;
|
|
1219
1370
|
}
|
|
1220
1371
|
}
|
|
1221
1372
|
}
|
|
1222
|
-
function ref(path, derivation) {
|
|
1373
|
+
function ref(path, derivation, metadata = {}) {
|
|
1374
|
+
const { sourceKind = sourceKindOf(derivation), ...details } = metadata;
|
|
1223
1375
|
return {
|
|
1224
1376
|
path,
|
|
1225
1377
|
cssVar: toVar("ref", path),
|
|
1378
|
+
sourceKind,
|
|
1226
1379
|
seedRefs: seedRefsOf(derivation),
|
|
1380
|
+
...details,
|
|
1227
1381
|
derivation
|
|
1228
1382
|
};
|
|
1229
1383
|
}
|
|
@@ -1251,6 +1405,9 @@ function buildColorRampRecipes() {
|
|
|
1251
1405
|
seedRef,
|
|
1252
1406
|
step,
|
|
1253
1407
|
value: FIGMA_T2_SOLID_SCALE[name][step]
|
|
1408
|
+
}, {
|
|
1409
|
+
source: `Figma Color/qw/t2/${name}/${step}`,
|
|
1410
|
+
rationale: "OKLCH solid scale is design-calibrated; the Seed records color-family lineage, not the final step value."
|
|
1254
1411
|
}));
|
|
1255
1412
|
out.push(ref([
|
|
1256
1413
|
"color",
|
|
@@ -1277,13 +1434,14 @@ function buildSpacingRecipes() {
|
|
|
1277
1434
|
}));
|
|
1278
1435
|
return out;
|
|
1279
1436
|
}
|
|
1280
|
-
/** 圆角档位系数(sm/lg 从 base 派生;默认 base=8px → 4/8/14)。 */
|
|
1437
|
+
/** 圆角档位系数(sm/lg/xl 从 base 派生;默认 base=8px → 4/8/14/16)。 */
|
|
1281
1438
|
const RADIUS_FACTORS = {
|
|
1282
1439
|
sm: .5,
|
|
1283
1440
|
md: 1,
|
|
1284
|
-
lg: 1.75
|
|
1441
|
+
lg: 1.75,
|
|
1442
|
+
xl: 2
|
|
1285
1443
|
};
|
|
1286
|
-
/** 5. 圆角刻度:sm/md/lg 由 radius.base 按系数派生,full 直通独立裁切基准。 */
|
|
1444
|
+
/** 5. 圆角刻度:sm/md/lg/xl 由 radius.base 按系数派生,full 直通独立裁切基准。xl=2×base 对齐治理稿 Radius v2 的 T2 double。 */
|
|
1287
1445
|
function buildRadiusRecipes() {
|
|
1288
1446
|
return [
|
|
1289
1447
|
ref([
|
|
@@ -1312,6 +1470,15 @@ function buildRadiusRecipes() {
|
|
|
1312
1470
|
seedRef: "size.radius.base",
|
|
1313
1471
|
factor: RADIUS_FACTORS.lg
|
|
1314
1472
|
}),
|
|
1473
|
+
ref([
|
|
1474
|
+
"size",
|
|
1475
|
+
"radius",
|
|
1476
|
+
"xl"
|
|
1477
|
+
], {
|
|
1478
|
+
kind: "scale",
|
|
1479
|
+
seedRef: "size.radius.base",
|
|
1480
|
+
factor: RADIUS_FACTORS.xl
|
|
1481
|
+
}),
|
|
1315
1482
|
ref([
|
|
1316
1483
|
"size",
|
|
1317
1484
|
"radius",
|
|
@@ -1384,16 +1551,108 @@ function buildControlIconSizeRecipes() {
|
|
|
1384
1551
|
})
|
|
1385
1552
|
];
|
|
1386
1553
|
}
|
|
1387
|
-
/**
|
|
1388
|
-
|
|
1389
|
-
|
|
1554
|
+
/** 描边宽度梯度系数(Figma Stroke v1:Half/Base/Double/Triple = 0.5/1/2/3)。 */
|
|
1555
|
+
const STROKE_WIDTH_FACTORS = {
|
|
1556
|
+
half: .5,
|
|
1557
|
+
double: 2,
|
|
1558
|
+
triple: 3
|
|
1559
|
+
};
|
|
1560
|
+
/** 7. 描边宽度(Figma Stroke v1):base 直通 Seed,half/double/triple 按系数派生。 */
|
|
1561
|
+
function buildStrokeWidthRecipes() {
|
|
1562
|
+
const seedRef = "size.borderWidth.hairline";
|
|
1563
|
+
return [
|
|
1564
|
+
ref([
|
|
1565
|
+
"size",
|
|
1566
|
+
"strokeWidth",
|
|
1567
|
+
"half"
|
|
1568
|
+
], {
|
|
1569
|
+
kind: "scale",
|
|
1570
|
+
seedRef,
|
|
1571
|
+
factor: STROKE_WIDTH_FACTORS.half
|
|
1572
|
+
}),
|
|
1573
|
+
ref([
|
|
1574
|
+
"size",
|
|
1575
|
+
"strokeWidth",
|
|
1576
|
+
"base"
|
|
1577
|
+
], {
|
|
1578
|
+
kind: "alias",
|
|
1579
|
+
seedRef
|
|
1580
|
+
}),
|
|
1581
|
+
ref([
|
|
1582
|
+
"size",
|
|
1583
|
+
"strokeWidth",
|
|
1584
|
+
"double"
|
|
1585
|
+
], {
|
|
1586
|
+
kind: "scale",
|
|
1587
|
+
seedRef,
|
|
1588
|
+
factor: STROKE_WIDTH_FACTORS.double
|
|
1589
|
+
}),
|
|
1590
|
+
ref([
|
|
1591
|
+
"size",
|
|
1592
|
+
"strokeWidth",
|
|
1593
|
+
"triple"
|
|
1594
|
+
], {
|
|
1595
|
+
kind: "scale",
|
|
1596
|
+
seedRef,
|
|
1597
|
+
factor: STROKE_WIDTH_FACTORS.triple
|
|
1598
|
+
})
|
|
1599
|
+
];
|
|
1600
|
+
}
|
|
1601
|
+
/** 六档动作控件高度(Figma Action Control v1):现有公开尺寸契约,不能由单一 T1 Base 可靠派生,直接定义在 T2(T1 不设 Control Size)。 */
|
|
1602
|
+
const ACTION_CONTROL_HEIGHTS = {
|
|
1603
|
+
"2xs": 24,
|
|
1604
|
+
xs: 28,
|
|
1605
|
+
sm: 32,
|
|
1606
|
+
md: 36,
|
|
1607
|
+
lg: 42,
|
|
1608
|
+
xl: 50
|
|
1609
|
+
};
|
|
1610
|
+
/** 7b. 动作控件尺寸:六档高度 + 2XS 图标特例(Square 复用同档 Height,不另设 token)。 */
|
|
1611
|
+
function buildActionControlRecipes() {
|
|
1612
|
+
const out = [];
|
|
1613
|
+
for (const [step, value] of Object.entries(ACTION_CONTROL_HEIGHTS)) out.push(ref([
|
|
1390
1614
|
"size",
|
|
1391
|
-
"
|
|
1392
|
-
|
|
1615
|
+
"actionControlHeight",
|
|
1616
|
+
step
|
|
1393
1617
|
], {
|
|
1394
|
-
kind: "
|
|
1395
|
-
|
|
1396
|
-
}
|
|
1618
|
+
kind: "literal",
|
|
1619
|
+
value: `${value}px`
|
|
1620
|
+
}, {
|
|
1621
|
+
source: "Figma 千问三方——Token 改造 · Foundation/Action Control Standard v1",
|
|
1622
|
+
rationale: "六档高度 24/28/32/36/42/50 是现有公开尺寸契约,治理稿将其直接定义在 T2,T1 不设置 Control Size。"
|
|
1623
|
+
}));
|
|
1624
|
+
out.push(ref([
|
|
1625
|
+
"size",
|
|
1626
|
+
"actionControlIcon",
|
|
1627
|
+
"2xs"
|
|
1628
|
+
], {
|
|
1629
|
+
kind: "literal",
|
|
1630
|
+
value: "14px"
|
|
1631
|
+
}, {
|
|
1632
|
+
source: "Figma 千问三方——Token 改造 · Foundation/Action Control Standard v1",
|
|
1633
|
+
rationale: "2XS 图标 14px 是治理稿已标记的组件特例(不扩充全局 Icon Scale);视觉验证通过后优先归一化为 Icon Size/Small。"
|
|
1634
|
+
}));
|
|
1635
|
+
return out;
|
|
1636
|
+
}
|
|
1637
|
+
/** Avatar 三档直径(Figma 头像 · Avatar S/M/L = 40/50/58)。 */
|
|
1638
|
+
const AVATAR_DIAMETERS = {
|
|
1639
|
+
sm: 40,
|
|
1640
|
+
md: 50,
|
|
1641
|
+
lg: 58
|
|
1642
|
+
};
|
|
1643
|
+
/** 7c. 展示类专用尺寸:Avatar 直径(Icon Size 规范要求 Avatar 直径使用自己的语义,禁止借用 Icon/Control 刻度)。 */
|
|
1644
|
+
function buildAvatarDiameterRecipes() {
|
|
1645
|
+
return Object.entries(AVATAR_DIAMETERS).map(([step, value]) => ref([
|
|
1646
|
+
"size",
|
|
1647
|
+
"avatarDiameter",
|
|
1648
|
+
step
|
|
1649
|
+
], {
|
|
1650
|
+
kind: "literal",
|
|
1651
|
+
value: `${value}px`
|
|
1652
|
+
}, {
|
|
1653
|
+
source: "Figma 千问三方——Token 改造 · 头像 · Avatar(S/M/L)",
|
|
1654
|
+
rationale: "Avatar 直径 40/50/58 是展示类专用尺寸契约;Icon Size 规范明确 Avatar 直径必须使用各自语义,不由 T1 派生。"
|
|
1655
|
+
}));
|
|
1397
1656
|
}
|
|
1398
1657
|
/** 8. 行高(typography 类目):normal 直通 Seed,tight/relaxed = base ∓ 0.25。 */
|
|
1399
1658
|
function buildLineHeightRecipes() {
|
|
@@ -1465,14 +1724,15 @@ const FONT_FAMILY_KEYS = [
|
|
|
1465
1724
|
"textWindowsZh",
|
|
1466
1725
|
"textWindowsJa"
|
|
1467
1726
|
];
|
|
1468
|
-
/** 字号档位倍数(沿用既定视觉:14px 基准 → 12/16/20/24)。 */
|
|
1727
|
+
/** 字号档位倍数(沿用既定视觉:14px 基准 → 11/12/16/20/24;xs=11 对齐治理稿 T2 Font Size 2XS)。 */
|
|
1469
1728
|
const FONT_SIZE_FACTORS = {
|
|
1729
|
+
xs: .786,
|
|
1470
1730
|
sm: .857,
|
|
1471
1731
|
lg: 1.143,
|
|
1472
1732
|
xl: 1.429,
|
|
1473
1733
|
"2xl": 1.714
|
|
1474
1734
|
};
|
|
1475
|
-
/** 11.
|
|
1735
|
+
/** 11. 排版原子:字体族来自平台 Seed;字重是机械标准常量;字号按既定倍数派生。 */
|
|
1476
1736
|
function buildTypographyRecipes() {
|
|
1477
1737
|
const out = [];
|
|
1478
1738
|
for (const key of FONT_FAMILY_KEYS) out.push(ref([
|
|
@@ -1483,17 +1743,20 @@ function buildTypographyRecipes() {
|
|
|
1483
1743
|
kind: "fontStack",
|
|
1484
1744
|
seedRef: `typography.fontFamily.${key}`
|
|
1485
1745
|
}));
|
|
1486
|
-
for (const key of [
|
|
1487
|
-
"regular",
|
|
1488
|
-
"medium",
|
|
1489
|
-
"semibold"
|
|
1746
|
+
for (const [key, value] of [
|
|
1747
|
+
["regular", 400],
|
|
1748
|
+
["medium", 500],
|
|
1749
|
+
["semibold", 600]
|
|
1490
1750
|
]) out.push(ref([
|
|
1491
1751
|
"typography",
|
|
1492
1752
|
"fontWeight",
|
|
1493
1753
|
key
|
|
1494
1754
|
], {
|
|
1495
|
-
kind: "
|
|
1496
|
-
|
|
1755
|
+
kind: "literal",
|
|
1756
|
+
value
|
|
1757
|
+
}, {
|
|
1758
|
+
source: "CSS Fonts numeric font-weight scale",
|
|
1759
|
+
rationale: "Mechanical weight step; changing it is a semantic typography decision, not a global generator input."
|
|
1497
1760
|
}));
|
|
1498
1761
|
out.push(ref([
|
|
1499
1762
|
"typography",
|
|
@@ -1591,7 +1854,9 @@ function allReferenceRecipes() {
|
|
|
1591
1854
|
...buildSpacingRecipes(),
|
|
1592
1855
|
...buildRadiusRecipes(),
|
|
1593
1856
|
...buildControlIconSizeRecipes(),
|
|
1594
|
-
...
|
|
1857
|
+
...buildStrokeWidthRecipes(),
|
|
1858
|
+
...buildActionControlRecipes(),
|
|
1859
|
+
...buildAvatarDiameterRecipes(),
|
|
1595
1860
|
...buildLineHeightRecipes(),
|
|
1596
1861
|
...buildOpacityRecipes(),
|
|
1597
1862
|
...buildZIndexRecipes(),
|
|
@@ -1626,8 +1891,10 @@ function semVar(path) {
|
|
|
1626
1891
|
return toVar("sem", path);
|
|
1627
1892
|
}
|
|
1628
1893
|
const RADIUS_ROLES = {
|
|
1894
|
+
sm: "size.radius.sm",
|
|
1629
1895
|
control: "size.radius.md",
|
|
1630
1896
|
card: "size.radius.lg",
|
|
1897
|
+
xl: "size.radius.xl",
|
|
1631
1898
|
pill: "size.radius.full",
|
|
1632
1899
|
full: "size.radius.full"
|
|
1633
1900
|
};
|
|
@@ -1641,6 +1908,57 @@ const ICON_SIZE_ROLES = {
|
|
|
1641
1908
|
md: "size.iconSize.md",
|
|
1642
1909
|
lg: "size.iconSize.lg"
|
|
1643
1910
|
};
|
|
1911
|
+
const ACTION_CONTROL_HEIGHT_ROLES = {
|
|
1912
|
+
"2xs": "size.actionControlHeight.2xs",
|
|
1913
|
+
xs: "size.actionControlHeight.xs",
|
|
1914
|
+
sm: "size.actionControlHeight.sm",
|
|
1915
|
+
md: "size.actionControlHeight.md",
|
|
1916
|
+
lg: "size.actionControlHeight.lg",
|
|
1917
|
+
xl: "size.actionControlHeight.xl"
|
|
1918
|
+
};
|
|
1919
|
+
const ACTION_CONTROL_PADDING_INLINE_ROLES = {
|
|
1920
|
+
"2xs": "size.spacing.2",
|
|
1921
|
+
xs: "size.spacing.2",
|
|
1922
|
+
sm: "size.spacing.4",
|
|
1923
|
+
md: "size.spacing.4",
|
|
1924
|
+
lg: "size.spacing.5",
|
|
1925
|
+
xl: "size.spacing.8"
|
|
1926
|
+
};
|
|
1927
|
+
const ACTION_CONTROL_ICON_ROLES = {
|
|
1928
|
+
"2xs": "size.actionControlIcon.2xs",
|
|
1929
|
+
xs: "size.iconSize.sm",
|
|
1930
|
+
sm: "size.iconSize.sm",
|
|
1931
|
+
md: "size.iconSize.sm",
|
|
1932
|
+
lg: "size.iconSize.sm",
|
|
1933
|
+
xl: "size.iconSize.md"
|
|
1934
|
+
};
|
|
1935
|
+
const ACTION_CONTROL_GAP_ROLES = {
|
|
1936
|
+
"2xs": "size.spacing.1",
|
|
1937
|
+
xs: "size.spacing.1",
|
|
1938
|
+
sm: "size.spacing.2",
|
|
1939
|
+
md: "size.spacing.2",
|
|
1940
|
+
lg: "size.spacing.2",
|
|
1941
|
+
xl: "size.spacing.2"
|
|
1942
|
+
};
|
|
1943
|
+
const BORDER_WIDTH_ROLES = {
|
|
1944
|
+
default: "size.strokeWidth.base",
|
|
1945
|
+
strong: "size.strokeWidth.double"
|
|
1946
|
+
};
|
|
1947
|
+
const DIVIDER_WIDTH_ROLES = {
|
|
1948
|
+
thin: "size.strokeWidth.half",
|
|
1949
|
+
default: "size.strokeWidth.base"
|
|
1950
|
+
};
|
|
1951
|
+
const FOCUS_RING_ROLES = {
|
|
1952
|
+
width: "size.strokeWidth.double",
|
|
1953
|
+
offset: "size.strokeWidth.double"
|
|
1954
|
+
};
|
|
1955
|
+
const INDICATOR_ROLES = { loading: "size.strokeWidth.double" };
|
|
1956
|
+
/** 展示类专用尺寸:Avatar 直径三档(Figma 头像 S/M/L)。 */
|
|
1957
|
+
const AVATAR_DIAMETER_ROLES = {
|
|
1958
|
+
sm: "size.avatarDiameter.sm",
|
|
1959
|
+
md: "size.avatarDiameter.md",
|
|
1960
|
+
lg: "size.avatarDiameter.lg"
|
|
1961
|
+
};
|
|
1644
1962
|
const INSET_STACK_ROLES = {
|
|
1645
1963
|
sm: "size.spacing.2",
|
|
1646
1964
|
md: "size.spacing.4",
|
|
@@ -1708,6 +2026,22 @@ const TYPOGRAPHY_ROLES = {
|
|
|
1708
2026
|
lineHeightRef: "typography.lineHeight.normal",
|
|
1709
2027
|
letterSpacingRef: T_LETTER
|
|
1710
2028
|
},
|
|
2029
|
+
/** 大正文(治理稿 T3 字段「大正文 16px · T2 M」):行高沿用 canonical 比例引擎。 */
|
|
2030
|
+
bodyLg: {
|
|
2031
|
+
fontFamilyRef: "typography.fontFamily.text",
|
|
2032
|
+
fontSizeRef: "typography.fontSize.lg",
|
|
2033
|
+
fontWeightRef: "typography.fontWeight.regular",
|
|
2034
|
+
lineHeightRef: "typography.lineHeight.normal",
|
|
2035
|
+
letterSpacingRef: T_LETTER
|
|
2036
|
+
},
|
|
2037
|
+
/** 小标签(治理稿 Text/Label/Small = 11/500/16):字号取 T2 xs=11px。 */
|
|
2038
|
+
labelSm: {
|
|
2039
|
+
fontFamilyRef: "typography.fontFamily.text",
|
|
2040
|
+
fontSizeRef: "typography.fontSize.xs",
|
|
2041
|
+
fontWeightRef: "typography.fontWeight.medium",
|
|
2042
|
+
lineHeightRef: "typography.lineHeight.normal",
|
|
2043
|
+
letterSpacingRef: T_LETTER
|
|
2044
|
+
},
|
|
1711
2045
|
code: {
|
|
1712
2046
|
fontFamilyRef: "typography.fontFamily.code",
|
|
1713
2047
|
fontSizeRef: "typography.fontSize.sm",
|
|
@@ -1716,15 +2050,22 @@ const TYPOGRAPHY_ROLES = {
|
|
|
1716
2050
|
letterSpacingRef: T_LETTER
|
|
1717
2051
|
}
|
|
1718
2052
|
};
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2053
|
+
/**
|
|
2054
|
+
* 颜色角色的 Semantic 变量路径:绝大多数角色落在 `--qw-sem-color-*` 下;
|
|
2055
|
+
* `effect.shadow.*.color` 是复合阴影的颜色分量,对应 Figma 变量
|
|
2056
|
+
* `--qw-sem-effect-shadow-*-color`,不带 `color-` 前缀。
|
|
2057
|
+
*/
|
|
2058
|
+
function colorRoleSemPath(role) {
|
|
2059
|
+
const segs = role.split(".");
|
|
2060
|
+
return segs[0] === "effect" ? segs : ["color", ...segs];
|
|
2061
|
+
}
|
|
2062
|
+
const COLOR_ROLES = { ...FIGMA_T3_COLOR_ROLES };
|
|
2063
|
+
/** 复合阴影角色:形状取 Reference effect.shadow.<level>,颜色取各自的 effect.shadow.<role>.color 分量。 */
|
|
2064
|
+
const SHADOW_COMPOSITE_ROLES = {
|
|
2065
|
+
card: "sm",
|
|
2066
|
+
floating: "md",
|
|
2067
|
+
dialog: "lg"
|
|
1725
2068
|
};
|
|
1726
|
-
/** 复合阴影角色:形状取 Reference effect.shadow.<level>,颜色取 color.shadow。 */
|
|
1727
|
-
const SHADOW_COMPOSITE_ROLES = { dialog: "lg" };
|
|
1728
2069
|
/**
|
|
1729
2070
|
* 全量 Semantic 角色的结构化血缘表——"角色 → 引用的 Reference 路径(含 light/dark 分支)"。
|
|
1730
2071
|
* 与 buildSemanticStructural/buildSemanticColor 的产出一一对应(顺序一致)。
|
|
@@ -1752,6 +2093,51 @@ function semanticRoleEntries() {
|
|
|
1752
2093
|
"iconSize",
|
|
1753
2094
|
role
|
|
1754
2095
|
], refPath);
|
|
2096
|
+
for (const [role, refPath] of Object.entries(ACTION_CONTROL_HEIGHT_ROLES)) structural([
|
|
2097
|
+
"size",
|
|
2098
|
+
"actionControlHeight",
|
|
2099
|
+
role
|
|
2100
|
+
], refPath);
|
|
2101
|
+
for (const [role, refPath] of Object.entries(ACTION_CONTROL_PADDING_INLINE_ROLES)) structural([
|
|
2102
|
+
"size",
|
|
2103
|
+
"actionControlPaddingInline",
|
|
2104
|
+
role
|
|
2105
|
+
], refPath);
|
|
2106
|
+
for (const [role, refPath] of Object.entries(ACTION_CONTROL_ICON_ROLES)) structural([
|
|
2107
|
+
"size",
|
|
2108
|
+
"actionControlIcon",
|
|
2109
|
+
role
|
|
2110
|
+
], refPath);
|
|
2111
|
+
for (const [role, refPath] of Object.entries(ACTION_CONTROL_GAP_ROLES)) structural([
|
|
2112
|
+
"size",
|
|
2113
|
+
"actionControlGap",
|
|
2114
|
+
role
|
|
2115
|
+
], refPath);
|
|
2116
|
+
for (const [role, refPath] of Object.entries(BORDER_WIDTH_ROLES)) structural([
|
|
2117
|
+
"size",
|
|
2118
|
+
"borderWidth",
|
|
2119
|
+
role
|
|
2120
|
+
], refPath);
|
|
2121
|
+
for (const [role, refPath] of Object.entries(DIVIDER_WIDTH_ROLES)) structural([
|
|
2122
|
+
"size",
|
|
2123
|
+
"dividerWidth",
|
|
2124
|
+
role
|
|
2125
|
+
], refPath);
|
|
2126
|
+
for (const [role, refPath] of Object.entries(FOCUS_RING_ROLES)) structural([
|
|
2127
|
+
"size",
|
|
2128
|
+
"focusRing",
|
|
2129
|
+
role
|
|
2130
|
+
], refPath);
|
|
2131
|
+
for (const [role, refPath] of Object.entries(INDICATOR_ROLES)) structural([
|
|
2132
|
+
"size",
|
|
2133
|
+
"indicator",
|
|
2134
|
+
role
|
|
2135
|
+
], refPath);
|
|
2136
|
+
for (const [role, refPath] of Object.entries(AVATAR_DIAMETER_ROLES)) structural([
|
|
2137
|
+
"size",
|
|
2138
|
+
"avatarDiameter",
|
|
2139
|
+
role
|
|
2140
|
+
], refPath);
|
|
1755
2141
|
for (const [role, refPath] of Object.entries(INSET_STACK_ROLES)) {
|
|
1756
2142
|
structural([
|
|
1757
2143
|
"size",
|
|
@@ -1817,7 +2203,7 @@ function semanticRoleEntries() {
|
|
|
1817
2203
|
"standard"
|
|
1818
2204
|
], "motion.easing.standard");
|
|
1819
2205
|
for (const [path, refs] of Object.entries(COLOR_ROLES)) {
|
|
1820
|
-
const segs =
|
|
2206
|
+
const segs = colorRoleSemPath(path);
|
|
1821
2207
|
out.push({
|
|
1822
2208
|
kind: "color",
|
|
1823
2209
|
path: segs.join("."),
|
|
@@ -1836,7 +2222,7 @@ function semanticRoleEntries() {
|
|
|
1836
2222
|
path: segs.join("."),
|
|
1837
2223
|
cssVar: semVar(segs),
|
|
1838
2224
|
shapeRef: `effect.shadow.${level}`,
|
|
1839
|
-
colorRole:
|
|
2225
|
+
colorRole: `effect.shadow.${role}.color`
|
|
1840
2226
|
});
|
|
1841
2227
|
}
|
|
1842
2228
|
return out;
|
|
@@ -1864,9 +2250,9 @@ function buildSemanticColor(reference, mode) {
|
|
|
1864
2250
|
if (entry.kind !== "color") continue;
|
|
1865
2251
|
out[entry.cssVar] = pick(tokens, entry.refPath[mode]);
|
|
1866
2252
|
}
|
|
1867
|
-
const shadowColor = out[semVar(["color", "shadow"])];
|
|
1868
2253
|
for (const [role, level] of Object.entries(SHADOW_COMPOSITE_ROLES)) {
|
|
1869
2254
|
const shape = shapes.shadow[level];
|
|
2255
|
+
const shadowColor = out[semVar(colorRoleSemPath(`effect.shadow.${role}.color`))];
|
|
1870
2256
|
out[semVar([
|
|
1871
2257
|
"effect",
|
|
1872
2258
|
"shadow",
|
|
@@ -1905,8 +2291,9 @@ function derive(seed) {
|
|
|
1905
2291
|
*
|
|
1906
2292
|
* 与静态 tokens.css(解析后的字面值,SSR 稳定出口)不同,live CSS 把整棵派生
|
|
1907
2293
|
* 树翻译成 CSS 原生表达式:
|
|
1908
|
-
* - T1 Seed 落为 `--qw-seed-*`
|
|
1909
|
-
* - T2 Reference
|
|
2294
|
+
* - T1 Seed 落为 `--qw-seed-*` 变量,承载需要全局级联的生成参数;
|
|
2295
|
+
* - T2 Reference 的 seed-derived 项用 relative color / calc() 派生,
|
|
2296
|
+
* design-calibrated / standard-constant 项保留 canonical 直接值;
|
|
1910
2297
|
* - T3 Semantic 全部是 `var(--qw-ref-*)` 别名;dark 模式只在暗色选择器下
|
|
1911
2298
|
* 重映射 sem → 不同的 ref 档位。
|
|
1912
2299
|
*
|
|
@@ -1914,11 +2301,11 @@ function derive(seed) {
|
|
|
1914
2301
|
* 级联更新,零 JS 重算。依赖现代 CSS(relative color / round()),刻意不做
|
|
1915
2302
|
* 老浏览器兼容——组件库消费方继续用静态 tokens.css。
|
|
1916
2303
|
*/
|
|
1917
|
-
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
2304
|
+
const round4$1 = (n) => Math.round(n * 1e4) / 1e4;
|
|
1918
2305
|
function seedVar(dotted) {
|
|
1919
2306
|
return toVar("seed", dotted.split("."));
|
|
1920
2307
|
}
|
|
1921
|
-
/** Seed 拍平成 `--qw-seed-*` → CSS
|
|
2308
|
+
/** Seed 拍平成 `--qw-seed-*` → CSS 值;T2 仍可包含校准值或标准常量。 */
|
|
1922
2309
|
function seedCssVars(seed) {
|
|
1923
2310
|
const out = {};
|
|
1924
2311
|
for (const [name, value] of Object.entries(seed.color)) out[seedVar(`color.${name}`)] = seedColorToCss(value);
|
|
@@ -1932,9 +2319,6 @@ function seedCssVars(seed) {
|
|
|
1932
2319
|
out[seedVar("size.opacity.alphaStep")] = String(seed.size.opacity.alphaStep);
|
|
1933
2320
|
out[seedVar("size.zIndex.step")] = String(seed.size.zIndex.step);
|
|
1934
2321
|
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
2322
|
out[seedVar("typography.fontSize.base")] = seed.typography.fontSize.base;
|
|
1939
2323
|
out[seedVar("typography.lineHeight.base")] = String(seed.typography.lineHeight.base);
|
|
1940
2324
|
out[seedVar("typography.letterSpacing.base")] = seed.typography.letterSpacing.base;
|
|
@@ -1972,13 +2356,13 @@ function derivationToCssExpr(d) {
|
|
|
1972
2356
|
case "offsetSteps": return `calc(var(${seedVar(d.seedRef)}) ${d.n >= 0 ? "+" : "-"} var(${seedVar(d.stepRef)}) * ${Math.abs(d.n)})`;
|
|
1973
2357
|
case "oklchRamp": {
|
|
1974
2358
|
const l = L_TARGETS[d.step];
|
|
1975
|
-
const f = round4(chromaFactor(l));
|
|
2359
|
+
const f = round4$1(chromaFactor(l));
|
|
1976
2360
|
return `oklch(from var(${seedVar(d.seedRef)}) ${l} calc(c * ${f}) h)`;
|
|
1977
2361
|
}
|
|
1978
2362
|
case "figmaOklchScale": return d.value;
|
|
1979
|
-
case "seedAlpha": return `rgb(from var(${seedVar(d.seedRef)}) r g b / ${round4(d.alpha)})`;
|
|
2363
|
+
case "seedAlpha": return `rgb(from var(${seedVar(d.seedRef)}) r g b / ${round4$1(d.alpha)})`;
|
|
1980
2364
|
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;
|
|
2365
|
+
case "literal": return String(d.value);
|
|
1982
2366
|
case "shadowLayer": {
|
|
1983
2367
|
const s = shadowShapeExprs(d.level);
|
|
1984
2368
|
const alpha = `calc(var(${seedVar("size.opacity.alphaStep")}) * ${SHADOW_LEVELS[d.level].alphaMult})`;
|
|
@@ -2021,212 +2405,347 @@ function buildLiveCss(seed, options = {}) {
|
|
|
2021
2405
|
].join("\n");
|
|
2022
2406
|
}
|
|
2023
2407
|
//#endregion
|
|
2024
|
-
//#region src/
|
|
2025
|
-
/**
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
});
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
targetSemanticRole: z.string().nullable(),
|
|
2084
|
-
status: compatStatusSchema,
|
|
2085
|
-
reviewAfter: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
2086
|
-
migrationIssue: z.string().nullable()
|
|
2087
|
-
}).superRefine((entry, ctx) => {
|
|
2088
|
-
if (entry.status === "aliased") {
|
|
2089
|
-
if (!("aliasOf" in entry.value)) ctx.addIssue({
|
|
2090
|
-
code: "custom",
|
|
2091
|
-
message: "status=aliased 时 value 必须为 { aliasOf }",
|
|
2092
|
-
path: ["value"]
|
|
2408
|
+
//#region src/figma/color-snapshot.ts
|
|
2409
|
+
/**
|
|
2410
|
+
* color-snapshot — 生成 Figma `Color` 变量集合的 DTCG 快照(design/color.tokens.json)。
|
|
2411
|
+
*
|
|
2412
|
+
* Figma 导出自动化通道尚未建立(变量 ID 本次不回填),快照由
|
|
2413
|
+
* color-architecture.ts 的同一份事实表投影生成,供 validate:figma-color 与
|
|
2414
|
+
* color-contract 测试消费;接上 Figma 导出后以导出物替换本生成物。
|
|
2415
|
+
*/
|
|
2416
|
+
const NS$1 = "com.qianwen.qw-ui";
|
|
2417
|
+
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
2418
|
+
function hexComponents(hex) {
|
|
2419
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex);
|
|
2420
|
+
if (!m) throw new Error(`[qw] invalid hex color: ${hex}`);
|
|
2421
|
+
const n = Number.parseInt(m[1], 16);
|
|
2422
|
+
return [
|
|
2423
|
+
round4((n >> 16 & 255) / 255),
|
|
2424
|
+
round4((n >> 8 & 255) / 255),
|
|
2425
|
+
round4((n & 255) / 255)
|
|
2426
|
+
];
|
|
2427
|
+
}
|
|
2428
|
+
function colorValue(hex, alpha = 1) {
|
|
2429
|
+
return {
|
|
2430
|
+
colorSpace: "srgb",
|
|
2431
|
+
components: hexComponents(hex),
|
|
2432
|
+
alpha
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function t1Ref(seed) {
|
|
2436
|
+
return `{qw.t1.${seed}}`;
|
|
2437
|
+
}
|
|
2438
|
+
/** T3 角色引用的 T2 点分路径("color.blue.a20" → "qw.t2.blue.a20")。 */
|
|
2439
|
+
function t2Ref(dotted) {
|
|
2440
|
+
const [, seed, step] = dotted.split(".");
|
|
2441
|
+
return `{qw.t2.${seed}.${step}}`;
|
|
2442
|
+
}
|
|
2443
|
+
function t2Token(value, provenance) {
|
|
2444
|
+
return {
|
|
2445
|
+
$type: "color",
|
|
2446
|
+
$value: value,
|
|
2447
|
+
$extensions: { [NS$1]: { provenance } }
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
function buildT1() {
|
|
2451
|
+
const t1 = {};
|
|
2452
|
+
for (const seed of T1_COLOR_KEYS) t1[seed] = {
|
|
2453
|
+
$type: "color",
|
|
2454
|
+
$value: colorValue(FIGMA_T1_SEED_HEX[seed])
|
|
2455
|
+
};
|
|
2456
|
+
return t1;
|
|
2457
|
+
}
|
|
2458
|
+
function buildT2() {
|
|
2459
|
+
const t2 = {};
|
|
2460
|
+
for (const seed of T1_COLOR_KEYS) {
|
|
2461
|
+
const seedHex = FIGMA_T1_SEED_HEX[seed];
|
|
2462
|
+
const steps = {};
|
|
2463
|
+
steps.base = t2Token(colorValue(seedHex), {
|
|
2464
|
+
sourceKind: "seed-alias",
|
|
2465
|
+
seedRefs: [t1Ref(seed)],
|
|
2466
|
+
formula: `= qw.t1.${seed}`
|
|
2093
2467
|
});
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2468
|
+
for (const alpha of T2_ALPHA_STEPS) steps[`a${alpha}`] = t2Token(colorValue(seedHex, alpha / 100), {
|
|
2469
|
+
sourceKind: "seed-derived",
|
|
2470
|
+
seedRefs: [t1Ref(seed)],
|
|
2471
|
+
formula: `= qw.t1.${seed} / ${alpha}%`
|
|
2098
2472
|
});
|
|
2473
|
+
for (const step of T2_OKLCH_STEPS) steps[String(step)] = t2Token(colorValue(FIGMA_T2_SOLID_SCALE[seed][step]), {
|
|
2474
|
+
sourceKind: "design-calibrated",
|
|
2475
|
+
seedRefs: [t1Ref(seed)],
|
|
2476
|
+
source: `Figma Color/qw/t2/${seed}/${step}`,
|
|
2477
|
+
formula: `= figma Color/qw/t2/${seed}/${step}(OKLCH 设计校准)`
|
|
2478
|
+
});
|
|
2479
|
+
t2[seed] = steps;
|
|
2099
2480
|
}
|
|
2100
|
-
|
|
2101
|
-
code: "custom",
|
|
2102
|
-
message: "status=active 时不得使用 aliasOf",
|
|
2103
|
-
path: ["value"]
|
|
2104
|
-
});
|
|
2105
|
-
});
|
|
2106
|
-
const compatRegistrySchema = z.object({
|
|
2107
|
-
$description: z.string().optional(),
|
|
2108
|
-
version: z.literal(1),
|
|
2109
|
-
graceDays: z.number().int().positive().default(90),
|
|
2110
|
-
entries: z.array(compatEntrySchema).min(1)
|
|
2111
|
-
});
|
|
2112
|
-
/** path → CSS 变量名:`button.color.bg-soft-brand` → `--qw-compat-button-color-bg-soft-brand` */
|
|
2113
|
-
function compatPathToCssVar(path) {
|
|
2114
|
-
return `--qw-compat-${path.replace(/\./g, "-")}`;
|
|
2481
|
+
return t2;
|
|
2115
2482
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2483
|
+
function buildT3() {
|
|
2484
|
+
const t3 = {};
|
|
2485
|
+
for (const [role, refs] of Object.entries(FIGMA_T3_COLOR_ROLES)) {
|
|
2486
|
+
const segs = role.split(".");
|
|
2487
|
+
const node = {
|
|
2488
|
+
$type: "color",
|
|
2489
|
+
$value: t2Ref(refs.light),
|
|
2490
|
+
$extensions: { [NS$1]: {
|
|
2491
|
+
cssVar: toVar("sem", colorRoleSemPath(role)),
|
|
2492
|
+
modes: {
|
|
2493
|
+
light: t2Ref(refs.light),
|
|
2494
|
+
dark: t2Ref(refs.dark)
|
|
2495
|
+
}
|
|
2496
|
+
} }
|
|
2497
|
+
};
|
|
2498
|
+
let cur = t3;
|
|
2499
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
2500
|
+
const seg = segs[i];
|
|
2501
|
+
if (!(seg in cur)) cur[seg] = {};
|
|
2502
|
+
cur = cur[seg];
|
|
2503
|
+
}
|
|
2504
|
+
cur[segs[segs.length - 1]] = node;
|
|
2505
|
+
}
|
|
2506
|
+
return t3;
|
|
2119
2507
|
}
|
|
2120
|
-
/**
|
|
2121
|
-
function
|
|
2122
|
-
return
|
|
2508
|
+
/** 生成 design/color.tokens.json 的完整 JSON 对象(qw/t1/t2/t3 结构)。 */
|
|
2509
|
+
function buildFigmaColorSnapshot() {
|
|
2510
|
+
return {
|
|
2511
|
+
$schema: "https://www.designtokens.org/schemas/2025.10/format.json",
|
|
2512
|
+
$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。",
|
|
2513
|
+
$extensions: { [NS$1]: {
|
|
2514
|
+
collection: "Color",
|
|
2515
|
+
figmaNode: "22258:49986",
|
|
2516
|
+
generated: true,
|
|
2517
|
+
owner: "design-system"
|
|
2518
|
+
} },
|
|
2519
|
+
qw: {
|
|
2520
|
+
t1: buildT1(),
|
|
2521
|
+
t2: buildT2(),
|
|
2522
|
+
t3: buildT3()
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2123
2525
|
}
|
|
2124
2526
|
//#endregion
|
|
2125
|
-
//#region src/
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2527
|
+
//#region src/figma/color-contract.ts
|
|
2528
|
+
const T2_OKLCH_STEP_KEYS = T2_OKLCH_STEPS.map(String);
|
|
2529
|
+
const NS = "com.qianwen.qw-ui";
|
|
2530
|
+
const T1_SET = new Set(T1_COLOR_KEYS);
|
|
2531
|
+
const T2_ALPHA_SET = new Set(T2_ALPHA_KEYS);
|
|
2532
|
+
const T2_OKLCH_SET = new Set(T2_OKLCH_STEP_KEYS);
|
|
2533
|
+
const T3_SEGMENT = /^[a-z]+(?:-[a-z]+)*$/;
|
|
2534
|
+
const PROVENANCE_SOURCE_KINDS = /* @__PURE__ */ new Set([
|
|
2535
|
+
"seed-alias",
|
|
2536
|
+
"seed-derived",
|
|
2537
|
+
"design-calibrated",
|
|
2538
|
+
"standard-constant",
|
|
2539
|
+
"external-standard"
|
|
2540
|
+
]);
|
|
2541
|
+
const SEED_LINEAGE_KINDS = /* @__PURE__ */ new Set([
|
|
2542
|
+
"seed-alias",
|
|
2543
|
+
"seed-derived",
|
|
2544
|
+
"design-calibrated"
|
|
2545
|
+
]);
|
|
2546
|
+
function isObject(value) {
|
|
2547
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2548
|
+
}
|
|
2549
|
+
function collectLeaves(node, path = [], out = []) {
|
|
2550
|
+
if (!isObject(node)) return out;
|
|
2551
|
+
if ("$value" in node) {
|
|
2552
|
+
out.push({
|
|
2553
|
+
path,
|
|
2554
|
+
value: node.$value,
|
|
2555
|
+
extensions: isObject(node.$extensions) ? node.$extensions : {}
|
|
2556
|
+
});
|
|
2557
|
+
return out;
|
|
2140
2558
|
}
|
|
2141
|
-
const [
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
b
|
|
2147
|
-
].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
|
|
2148
|
-
return `rgba(${r}, ${g}, ${b}, ${roundAlpha(alpha)})`;
|
|
2149
|
-
}
|
|
2150
|
-
function roundAlpha(a) {
|
|
2151
|
-
return Math.round(a * 1e3) / 1e3;
|
|
2559
|
+
for (const [key, child] of Object.entries(node)) {
|
|
2560
|
+
if (key.startsWith("$")) continue;
|
|
2561
|
+
collectLeaves(child, [...path, key], out);
|
|
2562
|
+
}
|
|
2563
|
+
return out;
|
|
2152
2564
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
if (
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2565
|
+
function qwPath(path) {
|
|
2566
|
+
const index = path.indexOf("qw");
|
|
2567
|
+
return index >= 0 ? path.slice(index) : null;
|
|
2568
|
+
}
|
|
2569
|
+
function pathKey(path) {
|
|
2570
|
+
return path.join(".");
|
|
2571
|
+
}
|
|
2572
|
+
function refToPath(ref) {
|
|
2573
|
+
if (typeof ref !== "string") return null;
|
|
2574
|
+
const match = /^\{(.+)\}$/.exec(ref.trim());
|
|
2575
|
+
if (!match) return null;
|
|
2576
|
+
return match[1].split(/[./]/).filter(Boolean);
|
|
2577
|
+
}
|
|
2578
|
+
function extensionObject(leaf) {
|
|
2579
|
+
const ext = leaf.extensions[NS];
|
|
2580
|
+
return isObject(ext) ? ext : {};
|
|
2581
|
+
}
|
|
2582
|
+
function modesOf(leaf) {
|
|
2583
|
+
const ext = extensionObject(leaf);
|
|
2584
|
+
return isObject(ext.modes) ? ext.modes : {};
|
|
2585
|
+
}
|
|
2586
|
+
function provenanceOf(leaf) {
|
|
2587
|
+
const ext = extensionObject(leaf);
|
|
2588
|
+
return isObject(ext.provenance) ? ext.provenance : {};
|
|
2589
|
+
}
|
|
2590
|
+
function isSolidColorValue(value) {
|
|
2591
|
+
if (!isObject(value)) return false;
|
|
2592
|
+
if (value.colorSpace !== "srgb" && value.colorSpace !== "oklch") return false;
|
|
2593
|
+
if (!Array.isArray(value.components) || value.components.length !== 3) return false;
|
|
2594
|
+
return value.alpha === 1;
|
|
2595
|
+
}
|
|
2596
|
+
function isColorLikeValue(value) {
|
|
2597
|
+
if (!isObject(value)) return false;
|
|
2598
|
+
if (value.colorSpace !== "srgb" && value.colorSpace !== "oklch") return false;
|
|
2599
|
+
if (!Array.isArray(value.components) || value.components.length !== 3) return false;
|
|
2600
|
+
return typeof value.alpha === "number" && value.alpha >= 0 && value.alpha <= 1;
|
|
2601
|
+
}
|
|
2602
|
+
function validateNoUnexpectedName(path, errors) {
|
|
2603
|
+
const joined = pathKey(path);
|
|
2604
|
+
if (joined.includes("_")) errors.push(`${joined}: token name must not contain underscores`);
|
|
2605
|
+
if (path.some((segment) => /[A-Z]/.test(segment))) errors.push(`${joined}: token name must be lowercase`);
|
|
2606
|
+
}
|
|
2607
|
+
function validateT3Path(path, errors) {
|
|
2608
|
+
const joined = pathKey(path);
|
|
2609
|
+
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`);
|
|
2610
|
+
}
|
|
2611
|
+
/** T2 Color 固定为 base / Seed Alpha / OKLCH 三类标准档位。 */
|
|
2612
|
+
function classifyT2Step(step) {
|
|
2613
|
+
if (step === "base") return "base";
|
|
2614
|
+
if (T2_ALPHA_SET.has(step)) return "alpha";
|
|
2615
|
+
if (T2_OKLCH_SET.has(step)) return "oklch";
|
|
2616
|
+
return null;
|
|
2617
|
+
}
|
|
2618
|
+
/** 各 T2 步类对应的 provenance.sourceKind(血缘治理不变量)。 */
|
|
2619
|
+
const T2_STEP_SOURCE_KIND = {
|
|
2620
|
+
base: "seed-alias",
|
|
2621
|
+
alpha: "seed-derived",
|
|
2622
|
+
oklch: "design-calibrated"
|
|
2623
|
+
};
|
|
2624
|
+
function validateT2Provenance(leaf, seed, step, errors) {
|
|
2625
|
+
const joined = pathKey(leaf.path);
|
|
2626
|
+
const provenance = provenanceOf(leaf);
|
|
2627
|
+
if (Object.keys(provenance).length === 0) {
|
|
2628
|
+
errors.push(`${joined}: T2 token must declare $extensions.${NS}.provenance`);
|
|
2629
|
+
return;
|
|
2165
2630
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
function semRoleToCssVar(role) {
|
|
2171
|
-
const parts = role.split(".");
|
|
2172
|
-
return `--qw-sem-${parts[0]}-${parts.slice(1).join("-")}`;
|
|
2173
|
-
}
|
|
2174
|
-
function buildCompatFromRegistry(registry) {
|
|
2175
|
-
const light = {};
|
|
2176
|
-
const dark = {};
|
|
2177
|
-
const paths = {};
|
|
2178
|
-
const byComponent = {};
|
|
2179
|
-
for (const entry of registry.entries) {
|
|
2180
|
-
const cssVar = compatPathToCssVar(entry.path);
|
|
2181
|
-
paths[entry.path] = cssVar;
|
|
2182
|
-
const component = compatComponentOf(entry.path);
|
|
2183
|
-
const suffix = compatSuffixOf(entry.path);
|
|
2184
|
-
if (!byComponent[component]) byComponent[component] = [];
|
|
2185
|
-
byComponent[component].push(suffix);
|
|
2186
|
-
const resolve = (mode) => resolveEntryValue(entry, mode, semRoleToCssVar);
|
|
2187
|
-
if ("light" in entry.value && "dark" in entry.value) {
|
|
2188
|
-
light[cssVar] = resolve("light");
|
|
2189
|
-
const darkVal = resolve("dark");
|
|
2190
|
-
if (darkVal !== light[cssVar]) dark[cssVar] = darkVal;
|
|
2191
|
-
} else light[cssVar] = resolve("light");
|
|
2631
|
+
const { sourceKind, seedRefs } = provenance;
|
|
2632
|
+
if (typeof sourceKind !== "string" || !PROVENANCE_SOURCE_KINDS.has(sourceKind)) {
|
|
2633
|
+
errors.push(`${joined}: provenance.sourceKind must be one of ${[...PROVENANCE_SOURCE_KINDS].join(", ")}`);
|
|
2634
|
+
return;
|
|
2192
2635
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
};
|
|
2636
|
+
const stepKind = classifyT2Step(step);
|
|
2637
|
+
if (stepKind && T2_STEP_SOURCE_KIND[stepKind] !== sourceKind) errors.push(`${joined}: T2 ${stepKind} token must declare provenance.sourceKind = "${T2_STEP_SOURCE_KIND[stepKind]}"`);
|
|
2638
|
+
if (SEED_LINEAGE_KINDS.has(sourceKind)) {
|
|
2639
|
+
const expected = `{qw.t1.${seed}}`;
|
|
2640
|
+
if (!Array.isArray(seedRefs) || seedRefs.length !== 1 || seedRefs[0] !== expected) errors.push(`${joined}: provenance.seedRefs must be exactly ["${expected}"]`);
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
if (Array.isArray(seedRefs) && seedRefs.length > 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must not fabricate Seed lineage`);
|
|
2644
|
+
if (typeof provenance.source !== "string" || provenance.source.length === 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must declare provenance.source`);
|
|
2645
|
+
if (typeof provenance.rationale !== "string" || provenance.rationale.length === 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must declare provenance.rationale`);
|
|
2201
2646
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2647
|
+
/** T3 角色全集(qw 路径),由 FIGMA_T3_COLOR_ROLES 投影。 */
|
|
2648
|
+
function figmaT3RolePaths() {
|
|
2649
|
+
return Object.keys(FIGMA_T3_COLOR_ROLES).map((role) => pathKey([
|
|
2650
|
+
"qw",
|
|
2651
|
+
"t3",
|
|
2652
|
+
...role.split(".")
|
|
2653
|
+
]));
|
|
2654
|
+
}
|
|
2655
|
+
function validateFigmaColorTokens(doc) {
|
|
2656
|
+
const errors = [];
|
|
2657
|
+
const leaves = collectLeaves(doc).map((leaf) => ({
|
|
2658
|
+
...leaf,
|
|
2659
|
+
qwPath: qwPath(leaf.path)
|
|
2660
|
+
})).filter((leaf) => leaf.qwPath != null);
|
|
2661
|
+
const byQwPath = /* @__PURE__ */ new Map();
|
|
2662
|
+
for (const leaf of leaves) {
|
|
2663
|
+
const key = pathKey(leaf.qwPath);
|
|
2664
|
+
if (byQwPath.has(key)) errors.push(`${key}: duplicated token path`);
|
|
2665
|
+
byQwPath.set(key, leaf);
|
|
2666
|
+
validateNoUnexpectedName(leaf.qwPath, errors);
|
|
2667
|
+
}
|
|
2668
|
+
const t1 = leaves.filter((leaf) => leaf.qwPath[1] === "t1");
|
|
2669
|
+
const t2 = leaves.filter((leaf) => leaf.qwPath[1] === "t2");
|
|
2670
|
+
const t3 = leaves.filter((leaf) => leaf.qwPath[1] === "t3");
|
|
2671
|
+
const unknownLayers = leaves.filter((leaf) => ![
|
|
2672
|
+
"t1",
|
|
2673
|
+
"t2",
|
|
2674
|
+
"t3"
|
|
2675
|
+
].includes(leaf.qwPath[1] ?? ""));
|
|
2676
|
+
for (const leaf of unknownLayers) errors.push(`${pathKey(leaf.qwPath)}: unknown Color token layer`);
|
|
2677
|
+
const t1Keys = /* @__PURE__ */ new Set();
|
|
2678
|
+
for (const leaf of t1) {
|
|
2679
|
+
const [, , key, ...rest] = leaf.qwPath;
|
|
2680
|
+
const joined = pathKey(leaf.qwPath);
|
|
2681
|
+
if (rest.length > 0 || !key) errors.push(`${joined}: T1 token must be qw/t1/{seed}`);
|
|
2682
|
+
if (key && !T1_SET.has(key)) errors.push(`${joined}: unexpected T1 seed "${key}"`);
|
|
2683
|
+
if (!isSolidColorValue(leaf.value)) errors.push(`${joined}: T1 seed must be a solid color with alpha = 1`);
|
|
2684
|
+
if (key) t1Keys.add(key);
|
|
2218
2685
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2686
|
+
const missingT1 = T1_COLOR_KEYS.filter((key) => !t1Keys.has(key));
|
|
2687
|
+
const extraT1 = [...t1Keys].filter((key) => !T1_SET.has(key));
|
|
2688
|
+
if (missingT1.length) errors.push(`qw.t1: missing required seeds ${missingT1.join(", ")}`);
|
|
2689
|
+
if (extraT1.length) errors.push(`qw.t1: unexpected seeds ${extraT1.join(", ")}`);
|
|
2690
|
+
const t2BySeed = /* @__PURE__ */ new Map();
|
|
2691
|
+
for (const seed of T1_COLOR_KEYS) t2BySeed.set(seed, /* @__PURE__ */ new Set());
|
|
2692
|
+
for (const leaf of t2) {
|
|
2693
|
+
const [, , seed, step, ...rest] = leaf.qwPath;
|
|
2694
|
+
const joined = pathKey(leaf.qwPath);
|
|
2695
|
+
if (rest.length > 0 || !seed || !step) errors.push(`${joined}: T2 token must be qw/t2/{seed}/{base|alpha|oklch-step}`);
|
|
2696
|
+
if (seed && !T1_SET.has(seed)) errors.push(`${joined}: T2 seed "${seed}" must exist in T1`);
|
|
2697
|
+
const stepKind = step ? classifyT2Step(step) : null;
|
|
2698
|
+
if (step && !stepKind) errors.push(`${joined}: unsupported T2 step "${step}"`);
|
|
2699
|
+
if (!isColorLikeValue(leaf.value) && step !== "base") errors.push(`${joined}: T2 token must carry a concrete color value`);
|
|
2700
|
+
if (seed && step && T1_SET.has(seed) && stepKind) {
|
|
2701
|
+
t2BySeed.get(seed).add(step);
|
|
2702
|
+
validateT2Provenance(leaf, seed, step, errors);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
for (const seed of T1_COLOR_KEYS) {
|
|
2706
|
+
const steps = t2BySeed.get(seed);
|
|
2707
|
+
const missingAlpha = T2_ALPHA_KEYS.filter((step) => !steps.has(step));
|
|
2708
|
+
const missingOklch = T2_OKLCH_STEP_KEYS.filter((step) => !steps.has(step));
|
|
2709
|
+
const unexpected = [...steps].filter((step) => !classifyT2Step(step));
|
|
2710
|
+
if (!steps.has("base")) errors.push(`qw.t2.${seed}: missing base token`);
|
|
2711
|
+
if (missingAlpha.length) errors.push(`qw.t2.${seed}: missing alpha steps ${missingAlpha.join(", ")}`);
|
|
2712
|
+
if (missingOklch.length) errors.push(`qw.t2.${seed}: missing OKLCH steps ${missingOklch.join(", ")}`);
|
|
2713
|
+
if (unexpected.length) errors.push(`qw.t2.${seed}: unexpected steps ${unexpected.join(", ")}`);
|
|
2714
|
+
}
|
|
2715
|
+
const expectedT3 = new Set(figmaT3RolePaths());
|
|
2716
|
+
const actualT3 = /* @__PURE__ */ new Set();
|
|
2717
|
+
for (const leaf of t3) {
|
|
2718
|
+
const joined = pathKey(leaf.qwPath);
|
|
2719
|
+
actualT3.add(joined);
|
|
2720
|
+
validateT3Path(leaf.qwPath, errors);
|
|
2721
|
+
if (!expectedT3.has(joined)) errors.push(`${joined}: unexpected T3 role (not in the 60-role Figma set)`);
|
|
2722
|
+
const modes = modesOf(leaf);
|
|
2723
|
+
const light = refToPath(modes.light);
|
|
2724
|
+
const dark = refToPath(modes.dark);
|
|
2725
|
+
if (!light || !dark) {
|
|
2726
|
+
errors.push(`${joined}: T3 token must declare both light and dark mode references`);
|
|
2727
|
+
continue;
|
|
2728
|
+
}
|
|
2729
|
+
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`);
|
|
2730
|
+
else if (!byQwPath.has(pathKey(ref))) errors.push(`${joined}: ${modeName} mode references missing token {${pathKey(ref)}}`);
|
|
2226
2731
|
}
|
|
2227
|
-
|
|
2732
|
+
const missingT3 = [...expectedT3].filter((role) => !actualT3.has(role));
|
|
2733
|
+
if (missingT3.length) errors.push(`qw.t3: missing required roles ${missingT3.join(", ")}`);
|
|
2734
|
+
return {
|
|
2735
|
+
ok: errors.length === 0,
|
|
2736
|
+
errors,
|
|
2737
|
+
counts: {
|
|
2738
|
+
t1: t1.length,
|
|
2739
|
+
t2: t2.length,
|
|
2740
|
+
t3: t3.length
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
function assertFigmaColorTokens(doc) {
|
|
2745
|
+
const result = validateFigmaColorTokens(doc);
|
|
2746
|
+
if (!result.ok) throw new Error(`Invalid Figma Color tokens:\n${result.errors.map((error) => `- ${error}`).join("\n")}`);
|
|
2228
2747
|
}
|
|
2229
2748
|
//#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,
|
|
2749
|
+
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, assertFigmaColorTokens, buildFigmaColorSnapshot, buildLiveCss, buildReference, buildSemanticColor, buildSemanticStructural, contrastRatio, defaultSeed, derivationToCssExpr, derive, evaluateDerivation, figmaT3RolePaths, formatDerivation, hexToSeedColor, ramp, seedColorToCss, seedColorToHex, seedCssVars, seedRefsOf, seedValueAt, semanticRoleEntries, sourceKindOf, toVar, validateFigmaColorTokens, validateSeed };
|
|
2231
2750
|
|
|
2232
2751
|
//# sourceMappingURL=index.js.map
|