@qw-ui/tokens 0.3.0-next.4 → 0.3.0-next.42
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 +20 -53
- package/dist/color-architecture.d.ts +163 -2
- package/dist/derive/index.d.ts +3 -3
- package/dist/derive/live-css.d.ts +1 -1
- package/dist/derive/reference.d.ts +73 -13
- package/dist/derive/semantic.d.ts +24 -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 +1027 -366
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +3 -3
- package/dist/tailwind.css +85 -0
- package/dist/tokens.css +308 -218
- package/dist/tokens.json +290 -93
- package/dist/tokens.live.css +178 -9
- package/dist/tokens.ts +293 -131
- package/package.json +12 -29
- 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
|
@@ -474,6 +474,13 @@ const T2_OKLCH_STEPS = [
|
|
|
474
474
|
800,
|
|
475
475
|
900
|
|
476
476
|
];
|
|
477
|
+
/** RFC0012 approved local standard endpoint; Figma synchronization is pending. */
|
|
478
|
+
const T2_BLACK_CONTRAST_ENDPOINT = {
|
|
479
|
+
step: "contrast-endpoint",
|
|
480
|
+
value: "#000000",
|
|
481
|
+
source: "CSS Color sRGB black mathematical endpoint; specs/rfcs/0012-brand-solid-text-contrast.md",
|
|
482
|
+
rationale: "The zero sRGB endpoint is a standard constant for on-brand AA text, independent of Seed and existing color scales."
|
|
483
|
+
};
|
|
477
484
|
const FIGMA_T2_SOLID_SCALE = {
|
|
478
485
|
brand: {
|
|
479
486
|
50: "#F2F6FF",
|
|
@@ -620,6 +627,12 @@ const FIGMA_T2_SOLID_SCALE = {
|
|
|
620
627
|
900: "#200B00"
|
|
621
628
|
}
|
|
622
629
|
};
|
|
630
|
+
/**
|
|
631
|
+
* Figma「Color Token System」既有角色及用户批准的本地补充角色。
|
|
632
|
+
* 既有角色与 Figma 节点 22258:49986 对齐;新角色等待 Figma 同步,不冒充已回流。
|
|
633
|
+
* light/dark 均引用 T2;`effect.shadow.*.color` 为复合阴影的颜色
|
|
634
|
+
* 分量(对应 Figma 变量 `--qw-sem-effect-shadow-*-color`)。
|
|
635
|
+
*/
|
|
623
636
|
const FIGMA_T3_COLOR_ROLES = {
|
|
624
637
|
"surface.base": {
|
|
625
638
|
light: "color.white.base",
|
|
@@ -641,6 +654,30 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
641
654
|
light: "color.blue.50",
|
|
642
655
|
dark: "color.blue.800"
|
|
643
656
|
},
|
|
657
|
+
"surface.canvas": {
|
|
658
|
+
light: "color.white.base",
|
|
659
|
+
dark: "color.black.900"
|
|
660
|
+
},
|
|
661
|
+
"surface.sunken": {
|
|
662
|
+
light: "color.gray.100",
|
|
663
|
+
dark: "color.gray.800"
|
|
664
|
+
},
|
|
665
|
+
"surface.selected": {
|
|
666
|
+
light: "color.black.a5",
|
|
667
|
+
dark: "color.white.a10"
|
|
668
|
+
},
|
|
669
|
+
"surface.inverse": {
|
|
670
|
+
light: "color.black.800",
|
|
671
|
+
dark: "color.black.800"
|
|
672
|
+
},
|
|
673
|
+
"surface.canvas-subtle": {
|
|
674
|
+
light: "color.gray.50",
|
|
675
|
+
dark: "color.black.900"
|
|
676
|
+
},
|
|
677
|
+
"content.danger": {
|
|
678
|
+
light: "color.red.600",
|
|
679
|
+
dark: "color.red.300"
|
|
680
|
+
},
|
|
644
681
|
"content.primary": {
|
|
645
682
|
light: "color.black.base",
|
|
646
683
|
dark: "color.white.base"
|
|
@@ -661,6 +698,34 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
661
698
|
light: "color.gray.400",
|
|
662
699
|
dark: "color.gray.500"
|
|
663
700
|
},
|
|
701
|
+
"content.disabled": {
|
|
702
|
+
light: "color.black.a30",
|
|
703
|
+
dark: "color.white.a30"
|
|
704
|
+
},
|
|
705
|
+
"content.link": {
|
|
706
|
+
light: "color.blue.700",
|
|
707
|
+
dark: "color.blue.400"
|
|
708
|
+
},
|
|
709
|
+
"content.icon-subtle": {
|
|
710
|
+
light: "color.black.a5",
|
|
711
|
+
dark: "color.white.a10"
|
|
712
|
+
},
|
|
713
|
+
"content.inverse": {
|
|
714
|
+
light: "color.white.base",
|
|
715
|
+
dark: "color.black.base"
|
|
716
|
+
},
|
|
717
|
+
"content.on-solid": {
|
|
718
|
+
light: "color.white.base",
|
|
719
|
+
dark: "color.white.base"
|
|
720
|
+
},
|
|
721
|
+
"content.on-brand": {
|
|
722
|
+
light: "color.white.base",
|
|
723
|
+
dark: "color.black.contrast-endpoint"
|
|
724
|
+
},
|
|
725
|
+
"content.on-warning-subtle": {
|
|
726
|
+
light: "color.yellow.800",
|
|
727
|
+
dark: "color.yellow.200"
|
|
728
|
+
},
|
|
664
729
|
"border.default": {
|
|
665
730
|
light: "color.gray.100",
|
|
666
731
|
dark: "color.gray.700"
|
|
@@ -669,10 +734,26 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
669
734
|
light: "color.gray.300",
|
|
670
735
|
dark: "color.gray.600"
|
|
671
736
|
},
|
|
737
|
+
"border.control": {
|
|
738
|
+
light: "color.gray.600",
|
|
739
|
+
dark: "color.gray.base"
|
|
740
|
+
},
|
|
672
741
|
"border.focus": {
|
|
673
742
|
light: "color.brand.base",
|
|
674
743
|
dark: "color.brand.500"
|
|
675
744
|
},
|
|
745
|
+
"border.subtle": {
|
|
746
|
+
light: "color.black.a5",
|
|
747
|
+
dark: "color.white.a5"
|
|
748
|
+
},
|
|
749
|
+
"border.disabled": {
|
|
750
|
+
light: "color.black.a10",
|
|
751
|
+
dark: "color.white.a10"
|
|
752
|
+
},
|
|
753
|
+
"border.on-media": {
|
|
754
|
+
light: "color.white.base",
|
|
755
|
+
dark: "color.white.base"
|
|
756
|
+
},
|
|
676
757
|
"fill.subtle": {
|
|
677
758
|
light: "color.gray.50",
|
|
678
759
|
dark: "color.white.a20"
|
|
@@ -689,6 +770,10 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
689
770
|
light: "color.gray.50",
|
|
690
771
|
dark: "color.gray.900"
|
|
691
772
|
},
|
|
773
|
+
"fill.track": {
|
|
774
|
+
light: "color.black.a10",
|
|
775
|
+
dark: "color.white.a20"
|
|
776
|
+
},
|
|
692
777
|
"action.brand": {
|
|
693
778
|
light: "color.brand.base",
|
|
694
779
|
dark: "color.brand.500"
|
|
@@ -705,6 +790,38 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
705
790
|
light: "color.white.base",
|
|
706
791
|
dark: "color.black.base"
|
|
707
792
|
},
|
|
793
|
+
"control-action.brand.subtle": {
|
|
794
|
+
light: "color.brand.a10",
|
|
795
|
+
dark: "color.brand.a20"
|
|
796
|
+
},
|
|
797
|
+
"control-action.product.subtle": {
|
|
798
|
+
light: "color.blue.a10",
|
|
799
|
+
dark: "color.blue.a20"
|
|
800
|
+
},
|
|
801
|
+
"control-action.danger.default": {
|
|
802
|
+
light: "color.red.600",
|
|
803
|
+
dark: "color.red.600"
|
|
804
|
+
},
|
|
805
|
+
"control-action.danger.subtle": {
|
|
806
|
+
light: "color.red.a10",
|
|
807
|
+
dark: "color.red.a20"
|
|
808
|
+
},
|
|
809
|
+
"control-action.neutral.subtle": {
|
|
810
|
+
light: "color.black.a5",
|
|
811
|
+
dark: "color.white.a10"
|
|
812
|
+
},
|
|
813
|
+
"control-selection.brand": {
|
|
814
|
+
light: "color.brand.a10",
|
|
815
|
+
dark: "color.brand.a20"
|
|
816
|
+
},
|
|
817
|
+
"control-selection.product": {
|
|
818
|
+
light: "color.blue.a10",
|
|
819
|
+
dark: "color.blue.a20"
|
|
820
|
+
},
|
|
821
|
+
"selection.text-background": {
|
|
822
|
+
light: "color.blue.a20",
|
|
823
|
+
dark: "color.blue.a40"
|
|
824
|
+
},
|
|
708
825
|
"status.success": {
|
|
709
826
|
light: "color.green.base",
|
|
710
827
|
dark: "color.green.base"
|
|
@@ -721,13 +838,57 @@ const FIGMA_T3_COLOR_ROLES = {
|
|
|
721
838
|
light: "color.blue.base",
|
|
722
839
|
dark: "color.blue.600"
|
|
723
840
|
},
|
|
841
|
+
"status.success-subtle": {
|
|
842
|
+
light: "color.green.a10",
|
|
843
|
+
dark: "color.green.a20"
|
|
844
|
+
},
|
|
845
|
+
"status.warning-subtle": {
|
|
846
|
+
light: "color.yellow.a20",
|
|
847
|
+
dark: "color.yellow.a20"
|
|
848
|
+
},
|
|
849
|
+
"status.danger-subtle": {
|
|
850
|
+
light: "color.red.a10",
|
|
851
|
+
dark: "color.red.a20"
|
|
852
|
+
},
|
|
853
|
+
"status.info-subtle": {
|
|
854
|
+
light: "color.blue.a10",
|
|
855
|
+
dark: "color.blue.a20"
|
|
856
|
+
},
|
|
724
857
|
"overlay.scrim": {
|
|
725
858
|
light: "color.black.a50",
|
|
726
859
|
dark: "color.black.a70"
|
|
727
860
|
},
|
|
728
861
|
"overlay.guide": {
|
|
729
|
-
light: "color.
|
|
730
|
-
dark: "color.
|
|
862
|
+
light: "color.blue.a10",
|
|
863
|
+
dark: "color.blue.a20"
|
|
864
|
+
},
|
|
865
|
+
"overlay.media-subtle": {
|
|
866
|
+
light: "color.black.a40",
|
|
867
|
+
dark: "color.black.a40"
|
|
868
|
+
},
|
|
869
|
+
"overlay.media-strong": {
|
|
870
|
+
light: "color.black.a50",
|
|
871
|
+
dark: "color.black.a50"
|
|
872
|
+
},
|
|
873
|
+
"overlay.control-on-media": {
|
|
874
|
+
light: "color.black.a50",
|
|
875
|
+
dark: "color.white.a40"
|
|
876
|
+
},
|
|
877
|
+
shadow: {
|
|
878
|
+
light: "color.black.a10",
|
|
879
|
+
dark: "color.black.a50"
|
|
880
|
+
},
|
|
881
|
+
"effect.shadow.card.color": {
|
|
882
|
+
light: "color.black.a10",
|
|
883
|
+
dark: "color.black.a50"
|
|
884
|
+
},
|
|
885
|
+
"effect.shadow.floating.color": {
|
|
886
|
+
light: "color.black.a10",
|
|
887
|
+
dark: "color.black.a50"
|
|
888
|
+
},
|
|
889
|
+
"effect.shadow.dialog.color": {
|
|
890
|
+
light: "color.black.a10",
|
|
891
|
+
dark: "color.black.a50"
|
|
731
892
|
}
|
|
732
893
|
};
|
|
733
894
|
//#endregion
|
|
@@ -828,11 +989,11 @@ const cubicBezier = z.tuple([
|
|
|
828
989
|
const fontStack = z.array(z.string()).min(1);
|
|
829
990
|
const colorSeedShape = Object.fromEntries(T1_COLOR_KEYS.map((key) => [key, seedColor]));
|
|
830
991
|
/**
|
|
831
|
-
* SeedSchema — T1 Seed
|
|
992
|
+
* SeedSchema — T1 Seed 与兼容输入的运行时校验(RFC 0001 §2.1,v4 修订)。
|
|
832
993
|
*
|
|
833
994
|
* 五大类目 color/size/typography/motion/effect,字段结构对齐
|
|
834
|
-
* packages/tokens/design/seed.tokens.json
|
|
835
|
-
*
|
|
995
|
+
* packages/tokens/design/seed.tokens.json。部分已不再驱动 canonical T2 的字段暂时
|
|
996
|
+
* 保留为兼容输入;新 Reference 不得为了形式完整继续扩充伪 Seed。
|
|
836
997
|
*
|
|
837
998
|
* v3 变化:
|
|
838
999
|
* - 颜色全部结构化(DTCG Color module,支持 srgb/oklch)。
|
|
@@ -881,8 +1042,11 @@ base: z.string() }),
|
|
|
881
1042
|
textWindowsJa: fontStack
|
|
882
1043
|
}),
|
|
883
1044
|
fontWeight: z.object({
|
|
1045
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
884
1046
|
regular: z.number(),
|
|
1047
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
885
1048
|
medium: z.number(),
|
|
1049
|
+
/** @deprecated T2 Font Weight 已是 standard-constant;保留到下一次 Seed API 主版本清理。 */
|
|
886
1050
|
semibold: z.number()
|
|
887
1051
|
}),
|
|
888
1052
|
fontSize: z.object({ base: z.string() }),
|
|
@@ -1168,7 +1332,8 @@ function evaluateDerivation(d, seed) {
|
|
|
1168
1332
|
}
|
|
1169
1333
|
case "fontStack": return formatFontFamily(seedValueAt(seed, d.seedRef));
|
|
1170
1334
|
case "cubicBezier": return formatCubicBezier(seedValueAt(seed, d.seedRef));
|
|
1171
|
-
case "literal": return d.value;
|
|
1335
|
+
case "literal": return String(d.value);
|
|
1336
|
+
case "cssEnvironment": return `env(${d.name}, ${d.fallback})`;
|
|
1172
1337
|
case "shadowLayer": {
|
|
1173
1338
|
const { offsetX, offsetY, blur, spread } = shadowShapeOf(seed, d.level);
|
|
1174
1339
|
return `${offsetX} ${offsetY} ${blur} ${spread} ${rgba([
|
|
@@ -1193,10 +1358,23 @@ function seedRefsOf(d) {
|
|
|
1193
1358
|
case "cubicBezier": return [d.seedRef];
|
|
1194
1359
|
case "offsetSteps": return [d.seedRef, d.stepRef];
|
|
1195
1360
|
case "alphaScale": return [d.stepRef];
|
|
1196
|
-
case "literal":
|
|
1361
|
+
case "literal":
|
|
1362
|
+
case "cssEnvironment": return [];
|
|
1197
1363
|
case "shadowLayer": return ["effect.shadow.base", "size.opacity.alphaStep"];
|
|
1198
1364
|
}
|
|
1199
1365
|
}
|
|
1366
|
+
/** Derivation AST → 规范来源分类;作为 Figma/DTCG provenance 的唯一映射口。 */
|
|
1367
|
+
function sourceKindOf(d) {
|
|
1368
|
+
switch (d.kind) {
|
|
1369
|
+
case "alias":
|
|
1370
|
+
case "fontStack":
|
|
1371
|
+
case "cubicBezier": return "seed-alias";
|
|
1372
|
+
case "figmaOklchScale": return "design-calibrated";
|
|
1373
|
+
case "literal": return "standard-constant";
|
|
1374
|
+
case "cssEnvironment": return "external-standard";
|
|
1375
|
+
default: return "seed-derived";
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1200
1378
|
/** 人读派生公式(Token Studio 展示用)。 */
|
|
1201
1379
|
function formatDerivation(d) {
|
|
1202
1380
|
const sign = (n) => n >= 0 ? "+" : "−";
|
|
@@ -1212,18 +1390,22 @@ function formatDerivation(d) {
|
|
|
1212
1390
|
case "figmaOklchScale": return `= figma Color/qw/t2/${d.seedRef.split(".")[1]}/${d.step}(OKLCH 设计校准)`;
|
|
1213
1391
|
case "seedAlpha": return `= seed.${d.seedRef} / ${round2(d.alpha * 100)}%`;
|
|
1214
1392
|
case "alphaScale": return `= ${d.channel === "black" ? "黑" : "白"} / seed.${d.stepRef} × ${d.multiplier}`;
|
|
1215
|
-
case "literal": return `= ${d.value}
|
|
1393
|
+
case "literal": return `= ${d.value}(canonical 标准常量)`;
|
|
1394
|
+
case "cssEnvironment": return `= env(${d.name}, ${d.fallback})(UA external standard)`;
|
|
1216
1395
|
case "shadowLayer": {
|
|
1217
1396
|
const f = SHADOW_LEVELS[d.level];
|
|
1218
1397
|
return `= seed.effect.shadow.base 形状 × {offset ${f.offset}, blur ${f.blur}} · 色 = 黑 / alphaStep × ${f.alphaMult}`;
|
|
1219
1398
|
}
|
|
1220
1399
|
}
|
|
1221
1400
|
}
|
|
1222
|
-
function ref(path, derivation) {
|
|
1401
|
+
function ref(path, derivation, metadata = {}) {
|
|
1402
|
+
const { sourceKind = sourceKindOf(derivation), ...details } = metadata;
|
|
1223
1403
|
return {
|
|
1224
1404
|
path,
|
|
1225
1405
|
cssVar: toVar("ref", path),
|
|
1406
|
+
sourceKind,
|
|
1226
1407
|
seedRefs: seedRefsOf(derivation),
|
|
1408
|
+
...details,
|
|
1227
1409
|
derivation
|
|
1228
1410
|
};
|
|
1229
1411
|
}
|
|
@@ -1251,6 +1433,9 @@ function buildColorRampRecipes() {
|
|
|
1251
1433
|
seedRef,
|
|
1252
1434
|
step,
|
|
1253
1435
|
value: FIGMA_T2_SOLID_SCALE[name][step]
|
|
1436
|
+
}, {
|
|
1437
|
+
source: `Figma Color/qw/t2/${name}/${step}`,
|
|
1438
|
+
rationale: "OKLCH solid scale is design-calibrated; the Seed records color-family lineage, not the final step value."
|
|
1254
1439
|
}));
|
|
1255
1440
|
out.push(ref([
|
|
1256
1441
|
"color",
|
|
@@ -1261,11 +1446,32 @@ function buildColorRampRecipes() {
|
|
|
1261
1446
|
seedRef
|
|
1262
1447
|
}));
|
|
1263
1448
|
}
|
|
1449
|
+
out.push(ref([
|
|
1450
|
+
"color",
|
|
1451
|
+
"black",
|
|
1452
|
+
T2_BLACK_CONTRAST_ENDPOINT.step
|
|
1453
|
+
], {
|
|
1454
|
+
kind: "literal",
|
|
1455
|
+
value: T2_BLACK_CONTRAST_ENDPOINT.value
|
|
1456
|
+
}, {
|
|
1457
|
+
source: T2_BLACK_CONTRAST_ENDPOINT.source,
|
|
1458
|
+
rationale: T2_BLACK_CONTRAST_ENDPOINT.rationale
|
|
1459
|
+
}));
|
|
1264
1460
|
return out;
|
|
1265
1461
|
}
|
|
1266
1462
|
/** 4. 间距刻度 = spacing.step × N (N=1..8)。 */
|
|
1267
1463
|
function buildSpacingRecipes() {
|
|
1268
|
-
const out = [
|
|
1464
|
+
const out = [ref([
|
|
1465
|
+
"size",
|
|
1466
|
+
"spacing",
|
|
1467
|
+
"0"
|
|
1468
|
+
], {
|
|
1469
|
+
kind: "literal",
|
|
1470
|
+
value: "0px"
|
|
1471
|
+
}, {
|
|
1472
|
+
source: "CSS Values zero length",
|
|
1473
|
+
rationale: "A canonical zero needs no theme Seed dependency."
|
|
1474
|
+
})];
|
|
1269
1475
|
for (let n = 1; n <= 8; n++) out.push(ref([
|
|
1270
1476
|
"size",
|
|
1271
1477
|
"spacing",
|
|
@@ -1277,13 +1483,33 @@ function buildSpacingRecipes() {
|
|
|
1277
1483
|
}));
|
|
1278
1484
|
return out;
|
|
1279
1485
|
}
|
|
1280
|
-
|
|
1486
|
+
function buildSafeAreaInsetRecipes() {
|
|
1487
|
+
return [
|
|
1488
|
+
"top",
|
|
1489
|
+
"right",
|
|
1490
|
+
"bottom",
|
|
1491
|
+
"left"
|
|
1492
|
+
].map((edge) => ref([
|
|
1493
|
+
"size",
|
|
1494
|
+
"safeAreaInset",
|
|
1495
|
+
edge
|
|
1496
|
+
], {
|
|
1497
|
+
kind: "cssEnvironment",
|
|
1498
|
+
name: `safe-area-inset-${edge}`,
|
|
1499
|
+
fallback: "0px"
|
|
1500
|
+
}, {
|
|
1501
|
+
source: "https://www.w3.org/TR/css-env-1/#safe-area-insets",
|
|
1502
|
+
rationale: "Dynamic UA-owned viewport inset; a CSS expression, not a fixed device dimension or Seed input."
|
|
1503
|
+
}));
|
|
1504
|
+
}
|
|
1505
|
+
/** 圆角档位系数(sm/lg/xl 从 base 派生;默认 base=8px → 4/8/14/16)。 */
|
|
1281
1506
|
const RADIUS_FACTORS = {
|
|
1282
1507
|
sm: .5,
|
|
1283
1508
|
md: 1,
|
|
1284
|
-
lg: 1.75
|
|
1509
|
+
lg: 1.75,
|
|
1510
|
+
xl: 2
|
|
1285
1511
|
};
|
|
1286
|
-
/** 5. 圆角刻度:sm/md/lg 由 radius.base 按系数派生,full 直通独立裁切基准。 */
|
|
1512
|
+
/** 5. 圆角刻度:sm/md/lg/xl 由 radius.base 按系数派生,full 直通独立裁切基准。xl=2×base 对齐治理稿 Radius v2 的 T2 double。 */
|
|
1287
1513
|
function buildRadiusRecipes() {
|
|
1288
1514
|
return [
|
|
1289
1515
|
ref([
|
|
@@ -1312,6 +1538,15 @@ function buildRadiusRecipes() {
|
|
|
1312
1538
|
seedRef: "size.radius.base",
|
|
1313
1539
|
factor: RADIUS_FACTORS.lg
|
|
1314
1540
|
}),
|
|
1541
|
+
ref([
|
|
1542
|
+
"size",
|
|
1543
|
+
"radius",
|
|
1544
|
+
"xl"
|
|
1545
|
+
], {
|
|
1546
|
+
kind: "scale",
|
|
1547
|
+
seedRef: "size.radius.base",
|
|
1548
|
+
factor: RADIUS_FACTORS.xl
|
|
1549
|
+
}),
|
|
1315
1550
|
ref([
|
|
1316
1551
|
"size",
|
|
1317
1552
|
"radius",
|
|
@@ -1322,6 +1557,48 @@ function buildRadiusRecipes() {
|
|
|
1322
1557
|
})
|
|
1323
1558
|
];
|
|
1324
1559
|
}
|
|
1560
|
+
/** Existing public placeholder geometry: font-relative text and calibrated media. */
|
|
1561
|
+
function buildPlaceholderSizeRecipes() {
|
|
1562
|
+
return [ref([
|
|
1563
|
+
"size",
|
|
1564
|
+
"placeholderTextHeight",
|
|
1565
|
+
"base"
|
|
1566
|
+
], {
|
|
1567
|
+
kind: "literal",
|
|
1568
|
+
value: "1em"
|
|
1569
|
+
}, {
|
|
1570
|
+
source: "CSS Values font-relative em unit; qw-ui Skeleton public text default",
|
|
1571
|
+
rationale: "Text placeholder follows inherited font size without adding a theme seed."
|
|
1572
|
+
}), ref([
|
|
1573
|
+
"size",
|
|
1574
|
+
"placeholderMediaHeight",
|
|
1575
|
+
"base"
|
|
1576
|
+
], {
|
|
1577
|
+
kind: "literal",
|
|
1578
|
+
value: "120px"
|
|
1579
|
+
}, {
|
|
1580
|
+
source: "qw-ui Skeleton existing public media placeholder geometry; specs/036-native-display",
|
|
1581
|
+
rationale: "Preserves the existing calibrated media default independently of control/avatar scales."
|
|
1582
|
+
})];
|
|
1583
|
+
}
|
|
1584
|
+
/** Loading logo boxes follow the independent existing public size contract. */
|
|
1585
|
+
function buildLoadingSizeRecipes() {
|
|
1586
|
+
return Object.entries({
|
|
1587
|
+
sm: 32,
|
|
1588
|
+
md: 48,
|
|
1589
|
+
lg: 60
|
|
1590
|
+
}).map(([size, value]) => ref([
|
|
1591
|
+
"size",
|
|
1592
|
+
"loadingLogoSize",
|
|
1593
|
+
size
|
|
1594
|
+
], {
|
|
1595
|
+
kind: "literal",
|
|
1596
|
+
value: `${value}px`
|
|
1597
|
+
}, {
|
|
1598
|
+
source: "qw-ui Loading public logo-size contract; specs/037-native-loading",
|
|
1599
|
+
rationale: "Independent calibrated logo boxes must not change with unrelated control or icon size seeds."
|
|
1600
|
+
}));
|
|
1601
|
+
}
|
|
1325
1602
|
/** 6. 控件/图标尺寸:md = base 直通,sm/lg = base ∓ spacing.step × n(控件 ×2、图标 ×1)。 */
|
|
1326
1603
|
function buildControlIconSizeRecipes() {
|
|
1327
1604
|
const step = "size.spacing.step";
|
|
@@ -1384,16 +1661,108 @@ function buildControlIconSizeRecipes() {
|
|
|
1384
1661
|
})
|
|
1385
1662
|
];
|
|
1386
1663
|
}
|
|
1387
|
-
/**
|
|
1388
|
-
|
|
1389
|
-
|
|
1664
|
+
/** 描边宽度梯度系数(Figma Stroke v1:Half/Base/Double/Triple = 0.5/1/2/3)。 */
|
|
1665
|
+
const STROKE_WIDTH_FACTORS = {
|
|
1666
|
+
half: .5,
|
|
1667
|
+
double: 2,
|
|
1668
|
+
triple: 3
|
|
1669
|
+
};
|
|
1670
|
+
/** 7. 描边宽度(Figma Stroke v1):base 直通 Seed,half/double/triple 按系数派生。 */
|
|
1671
|
+
function buildStrokeWidthRecipes() {
|
|
1672
|
+
const seedRef = "size.borderWidth.hairline";
|
|
1673
|
+
return [
|
|
1674
|
+
ref([
|
|
1675
|
+
"size",
|
|
1676
|
+
"strokeWidth",
|
|
1677
|
+
"half"
|
|
1678
|
+
], {
|
|
1679
|
+
kind: "scale",
|
|
1680
|
+
seedRef,
|
|
1681
|
+
factor: STROKE_WIDTH_FACTORS.half
|
|
1682
|
+
}),
|
|
1683
|
+
ref([
|
|
1684
|
+
"size",
|
|
1685
|
+
"strokeWidth",
|
|
1686
|
+
"base"
|
|
1687
|
+
], {
|
|
1688
|
+
kind: "alias",
|
|
1689
|
+
seedRef
|
|
1690
|
+
}),
|
|
1691
|
+
ref([
|
|
1692
|
+
"size",
|
|
1693
|
+
"strokeWidth",
|
|
1694
|
+
"double"
|
|
1695
|
+
], {
|
|
1696
|
+
kind: "scale",
|
|
1697
|
+
seedRef,
|
|
1698
|
+
factor: STROKE_WIDTH_FACTORS.double
|
|
1699
|
+
}),
|
|
1700
|
+
ref([
|
|
1701
|
+
"size",
|
|
1702
|
+
"strokeWidth",
|
|
1703
|
+
"triple"
|
|
1704
|
+
], {
|
|
1705
|
+
kind: "scale",
|
|
1706
|
+
seedRef,
|
|
1707
|
+
factor: STROKE_WIDTH_FACTORS.triple
|
|
1708
|
+
})
|
|
1709
|
+
];
|
|
1710
|
+
}
|
|
1711
|
+
/** 六档动作控件高度(Figma Action Control v1):现有公开尺寸契约,不能由单一 T1 Base 可靠派生,直接定义在 T2(T1 不设 Control Size)。 */
|
|
1712
|
+
const ACTION_CONTROL_HEIGHTS = {
|
|
1713
|
+
"2xs": 24,
|
|
1714
|
+
xs: 28,
|
|
1715
|
+
sm: 32,
|
|
1716
|
+
md: 36,
|
|
1717
|
+
lg: 42,
|
|
1718
|
+
xl: 50
|
|
1719
|
+
};
|
|
1720
|
+
/** 7b. 动作控件尺寸:六档高度 + 2XS 图标特例(Square 复用同档 Height,不另设 token)。 */
|
|
1721
|
+
function buildActionControlRecipes() {
|
|
1722
|
+
const out = [];
|
|
1723
|
+
for (const [step, value] of Object.entries(ACTION_CONTROL_HEIGHTS)) out.push(ref([
|
|
1390
1724
|
"size",
|
|
1391
|
-
"
|
|
1392
|
-
|
|
1725
|
+
"actionControlHeight",
|
|
1726
|
+
step
|
|
1393
1727
|
], {
|
|
1394
|
-
kind: "
|
|
1395
|
-
|
|
1396
|
-
}
|
|
1728
|
+
kind: "literal",
|
|
1729
|
+
value: `${value}px`
|
|
1730
|
+
}, {
|
|
1731
|
+
source: "Figma 千问三方——Token 改造 · Foundation/Action Control Standard v1",
|
|
1732
|
+
rationale: "六档高度 24/28/32/36/42/50 是现有公开尺寸契约,治理稿将其直接定义在 T2,T1 不设置 Control Size。"
|
|
1733
|
+
}));
|
|
1734
|
+
out.push(ref([
|
|
1735
|
+
"size",
|
|
1736
|
+
"actionControlIcon",
|
|
1737
|
+
"2xs"
|
|
1738
|
+
], {
|
|
1739
|
+
kind: "literal",
|
|
1740
|
+
value: "14px"
|
|
1741
|
+
}, {
|
|
1742
|
+
source: "Figma 千问三方——Token 改造 · Foundation/Action Control Standard v1",
|
|
1743
|
+
rationale: "2XS 图标 14px 是治理稿已标记的组件特例(不扩充全局 Icon Scale);视觉验证通过后优先归一化为 Icon Size/Small。"
|
|
1744
|
+
}));
|
|
1745
|
+
return out;
|
|
1746
|
+
}
|
|
1747
|
+
/** Avatar 三档直径(Figma 头像 · Avatar S/M/L = 40/50/58)。 */
|
|
1748
|
+
const AVATAR_DIAMETERS = {
|
|
1749
|
+
sm: 40,
|
|
1750
|
+
md: 50,
|
|
1751
|
+
lg: 58
|
|
1752
|
+
};
|
|
1753
|
+
/** 7c. 展示类专用尺寸:Avatar 直径(Icon Size 规范要求 Avatar 直径使用自己的语义,禁止借用 Icon/Control 刻度)。 */
|
|
1754
|
+
function buildAvatarDiameterRecipes() {
|
|
1755
|
+
return Object.entries(AVATAR_DIAMETERS).map(([step, value]) => ref([
|
|
1756
|
+
"size",
|
|
1757
|
+
"avatarDiameter",
|
|
1758
|
+
step
|
|
1759
|
+
], {
|
|
1760
|
+
kind: "literal",
|
|
1761
|
+
value: `${value}px`
|
|
1762
|
+
}, {
|
|
1763
|
+
source: "Figma 千问三方——Token 改造 · 头像 · Avatar(S/M/L)",
|
|
1764
|
+
rationale: "Avatar 直径 40/50/58 是展示类专用尺寸契约;Icon Size 规范明确 Avatar 直径必须使用各自语义,不由 T1 派生。"
|
|
1765
|
+
}));
|
|
1397
1766
|
}
|
|
1398
1767
|
/** 8. 行高(typography 类目):normal 直通 Seed,tight/relaxed = base ∓ 0.25。 */
|
|
1399
1768
|
function buildLineHeightRecipes() {
|
|
@@ -1415,6 +1784,18 @@ function buildLineHeightRecipes() {
|
|
|
1415
1784
|
kind: "alias",
|
|
1416
1785
|
seedRef: "typography.lineHeight.base"
|
|
1417
1786
|
}),
|
|
1787
|
+
ref([
|
|
1788
|
+
"typography",
|
|
1789
|
+
"lineHeight",
|
|
1790
|
+
"comfortable"
|
|
1791
|
+
], {
|
|
1792
|
+
kind: "literal",
|
|
1793
|
+
value: 1.625
|
|
1794
|
+
}, {
|
|
1795
|
+
sourceKind: "design-calibrated",
|
|
1796
|
+
source: "Figma RihMzcoamEChrLxQa0xjmH / I22378:53258;17214:12764 / frozen scene-22378-53247-reference.tsx",
|
|
1797
|
+
rationale: "Exact 26px / 16px title ratio; independent of lineHeight Seed anchor."
|
|
1798
|
+
}),
|
|
1418
1799
|
ref([
|
|
1419
1800
|
"typography",
|
|
1420
1801
|
"lineHeight",
|
|
@@ -1465,14 +1846,15 @@ const FONT_FAMILY_KEYS = [
|
|
|
1465
1846
|
"textWindowsZh",
|
|
1466
1847
|
"textWindowsJa"
|
|
1467
1848
|
];
|
|
1468
|
-
/** 字号档位倍数(沿用既定视觉:14px 基准 → 12/16/20/24)。 */
|
|
1849
|
+
/** 字号档位倍数(沿用既定视觉:14px 基准 → 11/12/16/20/24;xs=11 对齐治理稿 T2 Font Size 2XS)。 */
|
|
1469
1850
|
const FONT_SIZE_FACTORS = {
|
|
1851
|
+
xs: .786,
|
|
1470
1852
|
sm: .857,
|
|
1471
1853
|
lg: 1.143,
|
|
1472
1854
|
xl: 1.429,
|
|
1473
1855
|
"2xl": 1.714
|
|
1474
1856
|
};
|
|
1475
|
-
/** 11.
|
|
1857
|
+
/** 11. 排版原子:字体族来自平台 Seed;字重是机械标准常量;字号按既定倍数派生。 */
|
|
1476
1858
|
function buildTypographyRecipes() {
|
|
1477
1859
|
const out = [];
|
|
1478
1860
|
for (const key of FONT_FAMILY_KEYS) out.push(ref([
|
|
@@ -1483,17 +1865,20 @@ function buildTypographyRecipes() {
|
|
|
1483
1865
|
kind: "fontStack",
|
|
1484
1866
|
seedRef: `typography.fontFamily.${key}`
|
|
1485
1867
|
}));
|
|
1486
|
-
for (const key of [
|
|
1487
|
-
"regular",
|
|
1488
|
-
"medium",
|
|
1489
|
-
"semibold"
|
|
1868
|
+
for (const [key, value] of [
|
|
1869
|
+
["regular", 400],
|
|
1870
|
+
["medium", 500],
|
|
1871
|
+
["semibold", 600]
|
|
1490
1872
|
]) out.push(ref([
|
|
1491
1873
|
"typography",
|
|
1492
1874
|
"fontWeight",
|
|
1493
1875
|
key
|
|
1494
1876
|
], {
|
|
1495
|
-
kind: "
|
|
1496
|
-
|
|
1877
|
+
kind: "literal",
|
|
1878
|
+
value
|
|
1879
|
+
}, {
|
|
1880
|
+
source: "CSS Fonts numeric font-weight scale",
|
|
1881
|
+
rationale: "Mechanical weight step; changing it is a semantic typography decision, not a global generator input."
|
|
1497
1882
|
}));
|
|
1498
1883
|
out.push(ref([
|
|
1499
1884
|
"typography",
|
|
@@ -1557,6 +1942,37 @@ function buildMotionRecipes() {
|
|
|
1557
1942
|
seedRef: "motion.duration.base",
|
|
1558
1943
|
factor: DURATION_FACTORS.slow
|
|
1559
1944
|
}),
|
|
1945
|
+
ref([
|
|
1946
|
+
"motion",
|
|
1947
|
+
"duration",
|
|
1948
|
+
"shimmer"
|
|
1949
|
+
], {
|
|
1950
|
+
kind: "scale",
|
|
1951
|
+
seedRef: "motion.duration.base",
|
|
1952
|
+
factor: 10
|
|
1953
|
+
}),
|
|
1954
|
+
ref([
|
|
1955
|
+
"motion",
|
|
1956
|
+
"easing",
|
|
1957
|
+
"linear"
|
|
1958
|
+
], {
|
|
1959
|
+
kind: "literal",
|
|
1960
|
+
value: "linear"
|
|
1961
|
+
}, {
|
|
1962
|
+
source: "CSS Easing Functions: linear identity function",
|
|
1963
|
+
rationale: "Uniform decorative spin without an entrance or symmetric pulse curve."
|
|
1964
|
+
}),
|
|
1965
|
+
ref([
|
|
1966
|
+
"motion",
|
|
1967
|
+
"easing",
|
|
1968
|
+
"symmetric"
|
|
1969
|
+
], {
|
|
1970
|
+
kind: "literal",
|
|
1971
|
+
value: "ease-in-out"
|
|
1972
|
+
}, {
|
|
1973
|
+
source: "CSS Easing Functions: ease-in-out equals cubic-bezier(0.42, 0, 0.58, 1)",
|
|
1974
|
+
rationale: "Symmetric repeating shimmer preserves the existing easing; distinct from entrance easing."
|
|
1975
|
+
}),
|
|
1560
1976
|
ref([
|
|
1561
1977
|
"motion",
|
|
1562
1978
|
"easing",
|
|
@@ -1589,14 +2005,28 @@ function allReferenceRecipes() {
|
|
|
1589
2005
|
return [
|
|
1590
2006
|
...buildColorRampRecipes(),
|
|
1591
2007
|
...buildSpacingRecipes(),
|
|
2008
|
+
...buildSafeAreaInsetRecipes(),
|
|
2009
|
+
...buildPlaceholderSizeRecipes(),
|
|
2010
|
+
...buildLoadingSizeRecipes(),
|
|
1592
2011
|
...buildRadiusRecipes(),
|
|
1593
2012
|
...buildControlIconSizeRecipes(),
|
|
1594
|
-
...
|
|
2013
|
+
...buildStrokeWidthRecipes(),
|
|
2014
|
+
...buildActionControlRecipes(),
|
|
2015
|
+
...buildAvatarDiameterRecipes(),
|
|
1595
2016
|
...buildLineHeightRecipes(),
|
|
1596
2017
|
...buildOpacityRecipes(),
|
|
1597
2018
|
...buildZIndexRecipes(),
|
|
1598
2019
|
...buildTypographyRecipes(),
|
|
1599
2020
|
...buildMotionRecipes(),
|
|
2021
|
+
ref([
|
|
2022
|
+
"effect",
|
|
2023
|
+
"blur",
|
|
2024
|
+
"sm"
|
|
2025
|
+
], {
|
|
2026
|
+
kind: "scale",
|
|
2027
|
+
seedRef: "size.spacing.step",
|
|
2028
|
+
factor: .5
|
|
2029
|
+
}),
|
|
1600
2030
|
...buildShadowRecipes()
|
|
1601
2031
|
];
|
|
1602
2032
|
}
|
|
@@ -1625,106 +2055,205 @@ function pick(tokens, dottedRefPath) {
|
|
|
1625
2055
|
function semVar(path) {
|
|
1626
2056
|
return toVar("sem", path);
|
|
1627
2057
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
2058
|
+
/** Create independent ordinary role tables; prefix compression preserves each literal reference. */
|
|
2059
|
+
function referenceRoles(prefix, values) {
|
|
2060
|
+
return Object.fromEntries(Object.entries(values).map(([role, value]) => [role, `${prefix}.${value}`]));
|
|
2061
|
+
}
|
|
2062
|
+
const RADIUS_ROLES = /* @__PURE__ */ referenceRoles("size.radius", {
|
|
2063
|
+
sm: "sm",
|
|
2064
|
+
control: "md",
|
|
2065
|
+
card: "lg",
|
|
2066
|
+
xl: "xl",
|
|
2067
|
+
pill: "full",
|
|
2068
|
+
full: "full"
|
|
2069
|
+
});
|
|
2070
|
+
const CONTROL_HEIGHT_ROLES = /* @__PURE__ */ referenceRoles("size.controlSize", {
|
|
2071
|
+
sm: "sm",
|
|
2072
|
+
md: "md",
|
|
2073
|
+
lg: "lg"
|
|
2074
|
+
});
|
|
2075
|
+
const ICON_SIZE_ROLES = /* @__PURE__ */ referenceRoles("size.iconSize", {
|
|
2076
|
+
sm: "sm",
|
|
2077
|
+
md: "md",
|
|
2078
|
+
lg: "lg"
|
|
2079
|
+
});
|
|
2080
|
+
const ACTION_CONTROL_HEIGHT_ROLES = /* @__PURE__ */ referenceRoles("size.actionControlHeight", {
|
|
2081
|
+
"2xs": "2xs",
|
|
2082
|
+
xs: "xs",
|
|
2083
|
+
sm: "sm",
|
|
2084
|
+
md: "md",
|
|
2085
|
+
lg: "lg",
|
|
2086
|
+
xl: "xl"
|
|
2087
|
+
});
|
|
2088
|
+
const ACTION_CONTROL_PADDING_INLINE_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
|
|
2089
|
+
"2xs": "2",
|
|
2090
|
+
xs: "2",
|
|
2091
|
+
sm: "4",
|
|
2092
|
+
md: "4",
|
|
2093
|
+
lg: "5",
|
|
2094
|
+
xl: "8"
|
|
2095
|
+
});
|
|
2096
|
+
const ACTION_CONTROL_ICON_ROLES = {
|
|
2097
|
+
"2xs": "size.actionControlIcon.2xs",
|
|
2098
|
+
xs: "size.iconSize.sm",
|
|
1640
2099
|
sm: "size.iconSize.sm",
|
|
1641
|
-
md: "size.iconSize.
|
|
1642
|
-
lg: "size.iconSize.
|
|
1643
|
-
|
|
1644
|
-
const INSET_STACK_ROLES = {
|
|
1645
|
-
sm: "size.spacing.2",
|
|
1646
|
-
md: "size.spacing.4",
|
|
1647
|
-
lg: "size.spacing.6"
|
|
1648
|
-
};
|
|
1649
|
-
const Z_INDEX_ROLES = {
|
|
1650
|
-
dropdown: "size.zIndex.1",
|
|
1651
|
-
sticky: "size.zIndex.2",
|
|
1652
|
-
overlay: "size.zIndex.3",
|
|
1653
|
-
toast: "size.zIndex.4"
|
|
2100
|
+
md: "size.iconSize.sm",
|
|
2101
|
+
lg: "size.iconSize.sm",
|
|
2102
|
+
xl: "size.iconSize.md"
|
|
1654
2103
|
};
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
2104
|
+
const ACTION_CONTROL_GAP_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
|
|
2105
|
+
"2xs": "1",
|
|
2106
|
+
xs: "1",
|
|
2107
|
+
sm: "2",
|
|
2108
|
+
md: "2",
|
|
2109
|
+
lg: "2",
|
|
2110
|
+
xl: "2"
|
|
2111
|
+
});
|
|
2112
|
+
const BORDER_WIDTH_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", {
|
|
2113
|
+
default: "base",
|
|
2114
|
+
strong: "double"
|
|
2115
|
+
});
|
|
2116
|
+
const DIVIDER_WIDTH_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", {
|
|
2117
|
+
thin: "half",
|
|
2118
|
+
default: "base"
|
|
2119
|
+
});
|
|
2120
|
+
const FOCUS_RING_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", {
|
|
2121
|
+
width: "double",
|
|
2122
|
+
offset: "double"
|
|
2123
|
+
});
|
|
2124
|
+
const INDICATOR_ROLES = /* @__PURE__ */ referenceRoles("size.strokeWidth", { loading: "double" });
|
|
2125
|
+
/** 展示类专用尺寸:Avatar 直径三档(Figma 头像 S/M/L)。 */
|
|
2126
|
+
const AVATAR_DIAMETER_ROLES = /* @__PURE__ */ referenceRoles("size.avatarDiameter", {
|
|
2127
|
+
sm: "sm",
|
|
2128
|
+
md: "md",
|
|
2129
|
+
lg: "lg"
|
|
2130
|
+
});
|
|
2131
|
+
/** Independent placeholder roles reference T2 directly, never other T3 roles. */
|
|
2132
|
+
const SKELETON_SIZE_ROLES = {
|
|
2133
|
+
textHeight: "size.placeholderTextHeight.base",
|
|
2134
|
+
rectHeight: "size.placeholderMediaHeight.base",
|
|
2135
|
+
circleDiameter: "size.avatarDiameter.sm"
|
|
1659
2136
|
};
|
|
2137
|
+
const MOTION_EASING_ROLES = /* @__PURE__ */ referenceRoles("motion.easing", {
|
|
2138
|
+
standard: "standard",
|
|
2139
|
+
symmetric: "symmetric",
|
|
2140
|
+
linear: "linear"
|
|
2141
|
+
});
|
|
2142
|
+
const LOADING_SIZE_ROLES = /* @__PURE__ */ referenceRoles("size.loadingLogoSize", {
|
|
2143
|
+
sm: "sm",
|
|
2144
|
+
md: "md",
|
|
2145
|
+
lg: "lg"
|
|
2146
|
+
});
|
|
2147
|
+
const LAYOUT_GAP_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
|
|
2148
|
+
sm: "2",
|
|
2149
|
+
md: "4",
|
|
2150
|
+
lg: "6"
|
|
2151
|
+
});
|
|
2152
|
+
const SAFE_AREA_INSET_ROLES = /* @__PURE__ */ referenceRoles("size.safeAreaInset", {
|
|
2153
|
+
top: "top",
|
|
2154
|
+
right: "right",
|
|
2155
|
+
bottom: "bottom",
|
|
2156
|
+
left: "left"
|
|
2157
|
+
});
|
|
2158
|
+
const SAFE_AREA_MINIMUM_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
|
|
2159
|
+
none: "0",
|
|
2160
|
+
sm: "2",
|
|
2161
|
+
md: "4",
|
|
2162
|
+
lg: "6"
|
|
2163
|
+
});
|
|
2164
|
+
const INSET_STACK_ROLES = /* @__PURE__ */ referenceRoles("size.spacing", {
|
|
2165
|
+
sm: "2",
|
|
2166
|
+
md: "4",
|
|
2167
|
+
lg: "6"
|
|
2168
|
+
});
|
|
2169
|
+
const Z_INDEX_ROLES = /* @__PURE__ */ referenceRoles("size.zIndex", {
|
|
2170
|
+
dropdown: "1",
|
|
2171
|
+
sticky: "2",
|
|
2172
|
+
overlay: "3",
|
|
2173
|
+
toast: "4"
|
|
2174
|
+
});
|
|
2175
|
+
const MOTION_DURATION_ROLES = /* @__PURE__ */ referenceRoles("motion.duration", {
|
|
2176
|
+
fast: "fast",
|
|
2177
|
+
base: "base",
|
|
2178
|
+
slow: "slow",
|
|
2179
|
+
shimmer: "shimmer",
|
|
2180
|
+
spin: "shimmer"
|
|
2181
|
+
});
|
|
1660
2182
|
/** 字体族角色:text(正文缺省)/ number(数字数据)/ code(等宽)。 */
|
|
1661
|
-
const FONT_FAMILY_ROLES = {
|
|
1662
|
-
text: "
|
|
1663
|
-
number: "
|
|
1664
|
-
code: "
|
|
1665
|
-
};
|
|
2183
|
+
const FONT_FAMILY_ROLES = /* @__PURE__ */ referenceRoles("typography.fontFamily", {
|
|
2184
|
+
text: "text",
|
|
2185
|
+
number: "number",
|
|
2186
|
+
code: "code"
|
|
2187
|
+
});
|
|
1666
2188
|
const T_LETTER = "typography.letterSpacing.base";
|
|
2189
|
+
/** Fresh records retain independent mutable role values and original property order. */
|
|
2190
|
+
function typographyRole(size, weight = "regular", lineHeight = "normal", family = "text") {
|
|
2191
|
+
return {
|
|
2192
|
+
fontFamilyRef: `typography.fontFamily.${family}`,
|
|
2193
|
+
fontSizeRef: `typography.fontSize.${size}`,
|
|
2194
|
+
fontWeightRef: `typography.fontWeight.${weight}`,
|
|
2195
|
+
lineHeightRef: `typography.lineHeight.${lineHeight}`,
|
|
2196
|
+
letterSpacingRef: T_LETTER
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
1667
2199
|
/** typography.* 复合角色:每个字段各自引用 Reference(无任何裸字面量)。 */
|
|
1668
2200
|
const TYPOGRAPHY_ROLES = {
|
|
1669
|
-
body:
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
letterSpacingRef: T_LETTER
|
|
1682
|
-
},
|
|
1683
|
-
headingSm: {
|
|
1684
|
-
fontFamilyRef: "typography.fontFamily.text",
|
|
1685
|
-
fontSizeRef: "typography.fontSize.lg",
|
|
1686
|
-
fontWeightRef: "typography.fontWeight.semibold",
|
|
1687
|
-
lineHeightRef: "typography.lineHeight.tight",
|
|
1688
|
-
letterSpacingRef: T_LETTER
|
|
1689
|
-
},
|
|
1690
|
-
headingMd: {
|
|
1691
|
-
fontFamilyRef: "typography.fontFamily.text",
|
|
1692
|
-
fontSizeRef: "typography.fontSize.xl",
|
|
1693
|
-
fontWeightRef: "typography.fontWeight.semibold",
|
|
1694
|
-
lineHeightRef: "typography.lineHeight.tight",
|
|
1695
|
-
letterSpacingRef: T_LETTER
|
|
1696
|
-
},
|
|
1697
|
-
headingLg: {
|
|
1698
|
-
fontFamilyRef: "typography.fontFamily.text",
|
|
1699
|
-
fontSizeRef: "typography.fontSize.2xl",
|
|
1700
|
-
fontWeightRef: "typography.fontWeight.semibold",
|
|
1701
|
-
lineHeightRef: "typography.lineHeight.tight",
|
|
1702
|
-
letterSpacingRef: T_LETTER
|
|
1703
|
-
},
|
|
1704
|
-
caption: {
|
|
1705
|
-
fontFamilyRef: "typography.fontFamily.text",
|
|
1706
|
-
fontSizeRef: "typography.fontSize.sm",
|
|
1707
|
-
fontWeightRef: "typography.fontWeight.regular",
|
|
1708
|
-
lineHeightRef: "typography.lineHeight.normal",
|
|
1709
|
-
letterSpacingRef: T_LETTER
|
|
1710
|
-
},
|
|
1711
|
-
code: {
|
|
1712
|
-
fontFamilyRef: "typography.fontFamily.code",
|
|
1713
|
-
fontSizeRef: "typography.fontSize.sm",
|
|
1714
|
-
fontWeightRef: "typography.fontWeight.regular",
|
|
1715
|
-
lineHeightRef: "typography.lineHeight.normal",
|
|
1716
|
-
letterSpacingRef: T_LETTER
|
|
1717
|
-
}
|
|
2201
|
+
body: /* @__PURE__ */ typographyRole("base"),
|
|
2202
|
+
bodyStrong: /* @__PURE__ */ typographyRole("base", "medium"),
|
|
2203
|
+
productTitle: /* @__PURE__ */ typographyRole("lg", "semibold", "comfortable"),
|
|
2204
|
+
headingSm: /* @__PURE__ */ typographyRole("lg", "semibold", "tight"),
|
|
2205
|
+
headingMd: /* @__PURE__ */ typographyRole("xl", "semibold", "tight"),
|
|
2206
|
+
headingLg: /* @__PURE__ */ typographyRole("2xl", "semibold", "tight"),
|
|
2207
|
+
caption: /* @__PURE__ */ typographyRole("sm"),
|
|
2208
|
+
/** 大正文(治理稿 T3 字段「大正文 16px · T2 M」):行高沿用 canonical 比例引擎。 */
|
|
2209
|
+
bodyLg: /* @__PURE__ */ typographyRole("lg"),
|
|
2210
|
+
/** 小标签(治理稿 Text/Label/Small = 11/500/16):字号取 T2 xs=11px。 */
|
|
2211
|
+
labelSm: /* @__PURE__ */ typographyRole("xs", "medium"),
|
|
2212
|
+
code: /* @__PURE__ */ typographyRole("sm", "regular", "normal", "code")
|
|
1718
2213
|
};
|
|
1719
|
-
const
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
2214
|
+
const typographyProjectionFields = [...[
|
|
2215
|
+
"fontFamily",
|
|
2216
|
+
"fontSize",
|
|
2217
|
+
"fontWeight",
|
|
2218
|
+
"lineHeight",
|
|
2219
|
+
"letterSpacing"
|
|
2220
|
+
]];
|
|
2221
|
+
/**
|
|
2222
|
+
* 颜色角色的 Semantic 变量路径:绝大多数角色落在 `--qw-sem-color-*` 下;
|
|
2223
|
+
* `effect.shadow.*.color` 是复合阴影的颜色分量,对应 Figma 变量
|
|
2224
|
+
* `--qw-sem-effect-shadow-*-color`,不带 `color-` 前缀。
|
|
2225
|
+
*/
|
|
2226
|
+
function colorRoleSemPath(role) {
|
|
2227
|
+
const segs = role.split(".");
|
|
2228
|
+
return segs[0] === "effect" ? segs : ["color", ...segs];
|
|
2229
|
+
}
|
|
2230
|
+
const COLOR_ROLES = { ...FIGMA_T3_COLOR_ROLES };
|
|
2231
|
+
/** 复合阴影角色:形状取 Reference effect.shadow.<level>,颜色取各自的 effect.shadow.<role>.color 分量。 */
|
|
2232
|
+
const SHADOW_COMPOSITE_ROLES = {
|
|
2233
|
+
card: "sm",
|
|
2234
|
+
floating: "md",
|
|
2235
|
+
dialog: "lg"
|
|
1725
2236
|
};
|
|
1726
|
-
/**
|
|
1727
|
-
const
|
|
2237
|
+
/** Keep source table references live; preserve projection order without repeated loops. */
|
|
2238
|
+
const SIZE_ROLE_GROUPS = [
|
|
2239
|
+
["radius", RADIUS_ROLES],
|
|
2240
|
+
["controlHeight", CONTROL_HEIGHT_ROLES],
|
|
2241
|
+
["iconSize", ICON_SIZE_ROLES],
|
|
2242
|
+
["actionControlHeight", ACTION_CONTROL_HEIGHT_ROLES],
|
|
2243
|
+
["actionControlPaddingInline", ACTION_CONTROL_PADDING_INLINE_ROLES],
|
|
2244
|
+
["actionControlIcon", ACTION_CONTROL_ICON_ROLES],
|
|
2245
|
+
["actionControlGap", ACTION_CONTROL_GAP_ROLES],
|
|
2246
|
+
["borderWidth", BORDER_WIDTH_ROLES],
|
|
2247
|
+
["dividerWidth", DIVIDER_WIDTH_ROLES],
|
|
2248
|
+
["focusRing", FOCUS_RING_ROLES],
|
|
2249
|
+
["indicator", INDICATOR_ROLES],
|
|
2250
|
+
["avatarDiameter", AVATAR_DIAMETER_ROLES],
|
|
2251
|
+
["layoutGap", LAYOUT_GAP_ROLES],
|
|
2252
|
+
["safeAreaInset", SAFE_AREA_INSET_ROLES],
|
|
2253
|
+
["safeAreaMinimum", SAFE_AREA_MINIMUM_ROLES],
|
|
2254
|
+
["loading", LOADING_SIZE_ROLES],
|
|
2255
|
+
["skeleton", SKELETON_SIZE_ROLES]
|
|
2256
|
+
];
|
|
1728
2257
|
/**
|
|
1729
2258
|
* 全量 Semantic 角色的结构化血缘表——"角色 → 引用的 Reference 路径(含 light/dark 分支)"。
|
|
1730
2259
|
* 与 buildSemanticStructural/buildSemanticColor 的产出一一对应(顺序一致)。
|
|
@@ -1737,19 +2266,9 @@ function semanticRoleEntries() {
|
|
|
1737
2266
|
cssVar: semVar(path),
|
|
1738
2267
|
refPath
|
|
1739
2268
|
});
|
|
1740
|
-
for (const [role, refPath] of Object.entries(
|
|
1741
|
-
"size",
|
|
1742
|
-
"radius",
|
|
1743
|
-
role
|
|
1744
|
-
], refPath);
|
|
1745
|
-
for (const [role, refPath] of Object.entries(CONTROL_HEIGHT_ROLES)) structural([
|
|
2269
|
+
for (const [group, table] of SIZE_ROLE_GROUPS) for (const [role, refPath] of Object.entries(table)) structural([
|
|
1746
2270
|
"size",
|
|
1747
|
-
|
|
1748
|
-
role
|
|
1749
|
-
], refPath);
|
|
1750
|
-
for (const [role, refPath] of Object.entries(ICON_SIZE_ROLES)) structural([
|
|
1751
|
-
"size",
|
|
1752
|
-
"iconSize",
|
|
2271
|
+
group,
|
|
1753
2272
|
role
|
|
1754
2273
|
], refPath);
|
|
1755
2274
|
for (const [role, refPath] of Object.entries(INSET_STACK_ROLES)) {
|
|
@@ -1779,45 +2298,28 @@ function semanticRoleEntries() {
|
|
|
1779
2298
|
"fontFamily",
|
|
1780
2299
|
role
|
|
1781
2300
|
], refPath);
|
|
1782
|
-
for (const [role, spec] of Object.entries(TYPOGRAPHY_ROLES))
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
], spec.fontFamilyRef);
|
|
1788
|
-
structural([
|
|
1789
|
-
"typography",
|
|
1790
|
-
role,
|
|
1791
|
-
"fontSize"
|
|
1792
|
-
], spec.fontSizeRef);
|
|
1793
|
-
structural([
|
|
1794
|
-
"typography",
|
|
1795
|
-
role,
|
|
1796
|
-
"fontWeight"
|
|
1797
|
-
], spec.fontWeightRef);
|
|
1798
|
-
structural([
|
|
1799
|
-
"typography",
|
|
1800
|
-
role,
|
|
1801
|
-
"lineHeight"
|
|
1802
|
-
], spec.lineHeightRef);
|
|
1803
|
-
structural([
|
|
1804
|
-
"typography",
|
|
1805
|
-
role,
|
|
1806
|
-
"letterSpacing"
|
|
1807
|
-
], spec.letterSpacingRef);
|
|
1808
|
-
}
|
|
2301
|
+
for (const [role, spec] of Object.entries(TYPOGRAPHY_ROLES)) for (const field of typographyProjectionFields) structural([
|
|
2302
|
+
"typography",
|
|
2303
|
+
role,
|
|
2304
|
+
field
|
|
2305
|
+
], spec[`${field}Ref`]);
|
|
1809
2306
|
for (const [role, refPath] of Object.entries(MOTION_DURATION_ROLES)) structural([
|
|
1810
2307
|
"motion",
|
|
1811
2308
|
"duration",
|
|
1812
2309
|
role
|
|
1813
2310
|
], refPath);
|
|
1814
|
-
structural([
|
|
2311
|
+
for (const [role, refPath] of Object.entries(MOTION_EASING_ROLES)) structural([
|
|
1815
2312
|
"motion",
|
|
1816
2313
|
"easing",
|
|
1817
|
-
|
|
1818
|
-
],
|
|
2314
|
+
role
|
|
2315
|
+
], refPath);
|
|
2316
|
+
structural([
|
|
2317
|
+
"effect",
|
|
2318
|
+
"blur",
|
|
2319
|
+
"media-control"
|
|
2320
|
+
], "effect.blur.sm");
|
|
1819
2321
|
for (const [path, refs] of Object.entries(COLOR_ROLES)) {
|
|
1820
|
-
const segs =
|
|
2322
|
+
const segs = colorRoleSemPath(path);
|
|
1821
2323
|
out.push({
|
|
1822
2324
|
kind: "color",
|
|
1823
2325
|
path: segs.join("."),
|
|
@@ -1836,7 +2338,7 @@ function semanticRoleEntries() {
|
|
|
1836
2338
|
path: segs.join("."),
|
|
1837
2339
|
cssVar: semVar(segs),
|
|
1838
2340
|
shapeRef: `effect.shadow.${level}`,
|
|
1839
|
-
colorRole:
|
|
2341
|
+
colorRole: `effect.shadow.${role}.color`
|
|
1840
2342
|
});
|
|
1841
2343
|
}
|
|
1842
2344
|
return out;
|
|
@@ -1864,9 +2366,9 @@ function buildSemanticColor(reference, mode) {
|
|
|
1864
2366
|
if (entry.kind !== "color") continue;
|
|
1865
2367
|
out[entry.cssVar] = pick(tokens, entry.refPath[mode]);
|
|
1866
2368
|
}
|
|
1867
|
-
const shadowColor = out[semVar(["color", "shadow"])];
|
|
1868
2369
|
for (const [role, level] of Object.entries(SHADOW_COMPOSITE_ROLES)) {
|
|
1869
2370
|
const shape = shapes.shadow[level];
|
|
2371
|
+
const shadowColor = out[semVar(colorRoleSemPath(`effect.shadow.${role}.color`))];
|
|
1870
2372
|
out[semVar([
|
|
1871
2373
|
"effect",
|
|
1872
2374
|
"shadow",
|
|
@@ -1905,8 +2407,9 @@ function derive(seed) {
|
|
|
1905
2407
|
*
|
|
1906
2408
|
* 与静态 tokens.css(解析后的字面值,SSR 稳定出口)不同,live CSS 把整棵派生
|
|
1907
2409
|
* 树翻译成 CSS 原生表达式:
|
|
1908
|
-
* - T1 Seed 落为 `--qw-seed-*`
|
|
1909
|
-
* - T2 Reference
|
|
2410
|
+
* - T1 Seed 落为 `--qw-seed-*` 变量,承载需要全局级联的生成参数;
|
|
2411
|
+
* - T2 Reference 的 seed-derived 项用 relative color / calc() 派生,
|
|
2412
|
+
* design-calibrated / standard-constant 项保留 canonical 直接值;
|
|
1910
2413
|
* - T3 Semantic 全部是 `var(--qw-ref-*)` 别名;dark 模式只在暗色选择器下
|
|
1911
2414
|
* 重映射 sem → 不同的 ref 档位。
|
|
1912
2415
|
*
|
|
@@ -1914,11 +2417,11 @@ function derive(seed) {
|
|
|
1914
2417
|
* 级联更新,零 JS 重算。依赖现代 CSS(relative color / round()),刻意不做
|
|
1915
2418
|
* 老浏览器兼容——组件库消费方继续用静态 tokens.css。
|
|
1916
2419
|
*/
|
|
1917
|
-
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
2420
|
+
const round4$1 = (n) => Math.round(n * 1e4) / 1e4;
|
|
1918
2421
|
function seedVar(dotted) {
|
|
1919
2422
|
return toVar("seed", dotted.split("."));
|
|
1920
2423
|
}
|
|
1921
|
-
/** Seed 拍平成 `--qw-seed-*` → CSS
|
|
2424
|
+
/** Seed 拍平成 `--qw-seed-*` → CSS 值;T2 仍可包含校准值或标准常量。 */
|
|
1922
2425
|
function seedCssVars(seed) {
|
|
1923
2426
|
const out = {};
|
|
1924
2427
|
for (const [name, value] of Object.entries(seed.color)) out[seedVar(`color.${name}`)] = seedColorToCss(value);
|
|
@@ -1932,9 +2435,6 @@ function seedCssVars(seed) {
|
|
|
1932
2435
|
out[seedVar("size.opacity.alphaStep")] = String(seed.size.opacity.alphaStep);
|
|
1933
2436
|
out[seedVar("size.zIndex.step")] = String(seed.size.zIndex.step);
|
|
1934
2437
|
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
2438
|
out[seedVar("typography.fontSize.base")] = seed.typography.fontSize.base;
|
|
1939
2439
|
out[seedVar("typography.lineHeight.base")] = String(seed.typography.lineHeight.base);
|
|
1940
2440
|
out[seedVar("typography.letterSpacing.base")] = seed.typography.letterSpacing.base;
|
|
@@ -1972,13 +2472,14 @@ function derivationToCssExpr(d) {
|
|
|
1972
2472
|
case "offsetSteps": return `calc(var(${seedVar(d.seedRef)}) ${d.n >= 0 ? "+" : "-"} var(${seedVar(d.stepRef)}) * ${Math.abs(d.n)})`;
|
|
1973
2473
|
case "oklchRamp": {
|
|
1974
2474
|
const l = L_TARGETS[d.step];
|
|
1975
|
-
const f = round4(chromaFactor(l));
|
|
2475
|
+
const f = round4$1(chromaFactor(l));
|
|
1976
2476
|
return `oklch(from var(${seedVar(d.seedRef)}) ${l} calc(c * ${f}) h)`;
|
|
1977
2477
|
}
|
|
1978
2478
|
case "figmaOklchScale": return d.value;
|
|
1979
|
-
case "seedAlpha": return `rgb(from var(${seedVar(d.seedRef)}) r g b / ${round4(d.alpha)})`;
|
|
2479
|
+
case "seedAlpha": return `rgb(from var(${seedVar(d.seedRef)}) r g b / ${round4$1(d.alpha)})`;
|
|
1980
2480
|
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;
|
|
2481
|
+
case "literal": return String(d.value);
|
|
2482
|
+
case "cssEnvironment": return `env(${d.name}, ${d.fallback})`;
|
|
1982
2483
|
case "shadowLayer": {
|
|
1983
2484
|
const s = shadowShapeExprs(d.level);
|
|
1984
2485
|
const alpha = `calc(var(${seedVar("size.opacity.alphaStep")}) * ${SHADOW_LEVELS[d.level].alphaMult})`;
|
|
@@ -2021,212 +2522,372 @@ function buildLiveCss(seed, options = {}) {
|
|
|
2021
2522
|
].join("\n");
|
|
2022
2523
|
}
|
|
2023
2524
|
//#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"]
|
|
2525
|
+
//#region src/figma/color-snapshot.ts
|
|
2526
|
+
/**
|
|
2527
|
+
* color-snapshot — 生成 Figma `Color` 变量集合的 DTCG 快照(design/color.tokens.json)。
|
|
2528
|
+
*
|
|
2529
|
+
* Figma 导出自动化通道尚未建立(变量 ID 本次不回填),快照由
|
|
2530
|
+
* color-architecture.ts 的同一份事实表投影生成,供 validate:figma-color 与
|
|
2531
|
+
* color-contract 测试消费;接上 Figma 导出后以导出物替换本生成物。
|
|
2532
|
+
*/
|
|
2533
|
+
const NS$1 = "com.qianwen.qw-ui";
|
|
2534
|
+
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
2535
|
+
function hexComponents(hex) {
|
|
2536
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex);
|
|
2537
|
+
if (!m) throw new Error(`[qw] invalid hex color: ${hex}`);
|
|
2538
|
+
const n = Number.parseInt(m[1], 16);
|
|
2539
|
+
return [
|
|
2540
|
+
round4((n >> 16 & 255) / 255),
|
|
2541
|
+
round4((n >> 8 & 255) / 255),
|
|
2542
|
+
round4((n & 255) / 255)
|
|
2543
|
+
];
|
|
2544
|
+
}
|
|
2545
|
+
function colorValue(hex, alpha = 1) {
|
|
2546
|
+
return {
|
|
2547
|
+
colorSpace: "srgb",
|
|
2548
|
+
components: hexComponents(hex),
|
|
2549
|
+
alpha
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
2552
|
+
function t1Ref(seed) {
|
|
2553
|
+
return `{qw.t1.${seed}}`;
|
|
2554
|
+
}
|
|
2555
|
+
/** T3 角色引用的 T2 点分路径("color.blue.a20" → "qw.t2.blue.a20")。 */
|
|
2556
|
+
function t2Ref(dotted) {
|
|
2557
|
+
const [, seed, step] = dotted.split(".");
|
|
2558
|
+
return `{qw.t2.${seed}.${step}}`;
|
|
2559
|
+
}
|
|
2560
|
+
function t2Token(value, provenance) {
|
|
2561
|
+
return {
|
|
2562
|
+
$type: "color",
|
|
2563
|
+
$value: value,
|
|
2564
|
+
$extensions: { [NS$1]: { provenance } }
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
function buildT1() {
|
|
2568
|
+
const t1 = {};
|
|
2569
|
+
for (const seed of T1_COLOR_KEYS) t1[seed] = {
|
|
2570
|
+
$type: "color",
|
|
2571
|
+
$value: colorValue(FIGMA_T1_SEED_HEX[seed])
|
|
2572
|
+
};
|
|
2573
|
+
return t1;
|
|
2574
|
+
}
|
|
2575
|
+
function buildT2() {
|
|
2576
|
+
const t2 = {};
|
|
2577
|
+
for (const seed of T1_COLOR_KEYS) {
|
|
2578
|
+
const seedHex = FIGMA_T1_SEED_HEX[seed];
|
|
2579
|
+
const steps = {};
|
|
2580
|
+
steps.base = t2Token(colorValue(seedHex), {
|
|
2581
|
+
sourceKind: "seed-alias",
|
|
2582
|
+
seedRefs: [t1Ref(seed)],
|
|
2583
|
+
formula: `= qw.t1.${seed}`
|
|
2093
2584
|
});
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2585
|
+
for (const alpha of T2_ALPHA_STEPS) steps[`a${alpha}`] = t2Token(colorValue(seedHex, alpha / 100), {
|
|
2586
|
+
sourceKind: "seed-derived",
|
|
2587
|
+
seedRefs: [t1Ref(seed)],
|
|
2588
|
+
formula: `= qw.t1.${seed} / ${alpha}%`
|
|
2098
2589
|
});
|
|
2590
|
+
for (const step of T2_OKLCH_STEPS) steps[String(step)] = t2Token(colorValue(FIGMA_T2_SOLID_SCALE[seed][step]), {
|
|
2591
|
+
sourceKind: "design-calibrated",
|
|
2592
|
+
seedRefs: [t1Ref(seed)],
|
|
2593
|
+
source: `Figma Color/qw/t2/${seed}/${step}`,
|
|
2594
|
+
formula: `= figma Color/qw/t2/${seed}/${step}(OKLCH 设计校准)`
|
|
2595
|
+
});
|
|
2596
|
+
t2[seed] = steps;
|
|
2099
2597
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2598
|
+
t2.black[T2_BLACK_CONTRAST_ENDPOINT.step] = t2Token(colorValue(T2_BLACK_CONTRAST_ENDPOINT.value), {
|
|
2599
|
+
sourceKind: "standard-constant",
|
|
2600
|
+
seedRefs: [],
|
|
2601
|
+
source: T2_BLACK_CONTRAST_ENDPOINT.source,
|
|
2602
|
+
rationale: T2_BLACK_CONTRAST_ENDPOINT.rationale,
|
|
2603
|
+
formula: "= sRGB(0, 0, 0), alpha 1"
|
|
2104
2604
|
});
|
|
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, "-")}`;
|
|
2605
|
+
return t2;
|
|
2115
2606
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2607
|
+
function buildT3() {
|
|
2608
|
+
const t3 = {};
|
|
2609
|
+
for (const [role, refs] of Object.entries(FIGMA_T3_COLOR_ROLES)) {
|
|
2610
|
+
const segs = role.split(".");
|
|
2611
|
+
const approval = role === "content.on-brand" ? "RFC0012 user 2026-10-12" : role === "content.danger" ? "RFC0026" : role === "border.control" ? "SPEC085" : void 0;
|
|
2612
|
+
const node = {
|
|
2613
|
+
$type: "color",
|
|
2614
|
+
$value: t2Ref(refs.light),
|
|
2615
|
+
$extensions: { [NS$1]: {
|
|
2616
|
+
cssVar: toVar("sem", colorRoleSemPath(role)),
|
|
2617
|
+
modes: {
|
|
2618
|
+
light: t2Ref(refs.light),
|
|
2619
|
+
dark: t2Ref(refs.dark)
|
|
2620
|
+
},
|
|
2621
|
+
...approval ? {
|
|
2622
|
+
approval,
|
|
2623
|
+
figmaSync: "pending"
|
|
2624
|
+
} : {}
|
|
2625
|
+
} }
|
|
2626
|
+
};
|
|
2627
|
+
let cur = t3;
|
|
2628
|
+
for (let i = 0; i < segs.length - 1; i++) {
|
|
2629
|
+
const seg = segs[i];
|
|
2630
|
+
if (!(seg in cur)) cur[seg] = {};
|
|
2631
|
+
cur = cur[seg];
|
|
2632
|
+
}
|
|
2633
|
+
cur[segs[segs.length - 1]] = node;
|
|
2634
|
+
}
|
|
2635
|
+
return t3;
|
|
2119
2636
|
}
|
|
2120
|
-
/**
|
|
2121
|
-
function
|
|
2122
|
-
return
|
|
2637
|
+
/** 生成 design/color.tokens.json 的完整 JSON 对象(qw/t1/t2/t3 结构)。 */
|
|
2638
|
+
function buildFigmaColorSnapshot() {
|
|
2639
|
+
return {
|
|
2640
|
+
$schema: "https://www.designtokens.org/schemas/2025.10/format.json",
|
|
2641
|
+
$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。",
|
|
2642
|
+
$extensions: { [NS$1]: {
|
|
2643
|
+
collection: "Color",
|
|
2644
|
+
figmaNode: "22258:49986",
|
|
2645
|
+
generated: true,
|
|
2646
|
+
owner: "design-system",
|
|
2647
|
+
localExtensions: {
|
|
2648
|
+
rfc: "0012",
|
|
2649
|
+
figmaSync: "pending"
|
|
2650
|
+
}
|
|
2651
|
+
} },
|
|
2652
|
+
qw: {
|
|
2653
|
+
t1: buildT1(),
|
|
2654
|
+
t2: buildT2(),
|
|
2655
|
+
t3: buildT3()
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2123
2658
|
}
|
|
2124
2659
|
//#endregion
|
|
2125
|
-
//#region src/
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2660
|
+
//#region src/figma/color-contract.ts
|
|
2661
|
+
const T2_OKLCH_STEP_KEYS = T2_OKLCH_STEPS.map(String);
|
|
2662
|
+
const NS = "com.qianwen.qw-ui";
|
|
2663
|
+
const T1_SET = new Set(T1_COLOR_KEYS);
|
|
2664
|
+
const T2_ALPHA_SET = new Set(T2_ALPHA_KEYS);
|
|
2665
|
+
const T2_OKLCH_SET = new Set(T2_OKLCH_STEP_KEYS);
|
|
2666
|
+
const T3_SEGMENT = /^[a-z]+(?:-[a-z]+)*$/;
|
|
2667
|
+
const PROVENANCE_SOURCE_KINDS = /* @__PURE__ */ new Set([
|
|
2668
|
+
"seed-alias",
|
|
2669
|
+
"seed-derived",
|
|
2670
|
+
"design-calibrated",
|
|
2671
|
+
"standard-constant",
|
|
2672
|
+
"external-standard"
|
|
2673
|
+
]);
|
|
2674
|
+
const SEED_LINEAGE_KINDS = /* @__PURE__ */ new Set([
|
|
2675
|
+
"seed-alias",
|
|
2676
|
+
"seed-derived",
|
|
2677
|
+
"design-calibrated"
|
|
2678
|
+
]);
|
|
2679
|
+
function isObject(value) {
|
|
2680
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2681
|
+
}
|
|
2682
|
+
function collectLeaves(node, path = [], out = []) {
|
|
2683
|
+
if (!isObject(node)) return out;
|
|
2684
|
+
if ("$value" in node) {
|
|
2685
|
+
out.push({
|
|
2686
|
+
path,
|
|
2687
|
+
value: node.$value,
|
|
2688
|
+
extensions: isObject(node.$extensions) ? node.$extensions : {}
|
|
2689
|
+
});
|
|
2690
|
+
return out;
|
|
2691
|
+
}
|
|
2692
|
+
for (const [key, child] of Object.entries(node)) {
|
|
2693
|
+
if (key.startsWith("$")) continue;
|
|
2694
|
+
collectLeaves(child, [...path, key], out);
|
|
2140
2695
|
}
|
|
2141
|
-
|
|
2142
|
-
const alpha = input.alpha;
|
|
2143
|
-
if (alpha >= 1) return `#${[
|
|
2144
|
-
r,
|
|
2145
|
-
g,
|
|
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;
|
|
2696
|
+
return out;
|
|
2152
2697
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
if (
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2698
|
+
function qwPath(path) {
|
|
2699
|
+
const index = path.indexOf("qw");
|
|
2700
|
+
return index >= 0 ? path.slice(index) : null;
|
|
2701
|
+
}
|
|
2702
|
+
function pathKey(path) {
|
|
2703
|
+
return path.join(".");
|
|
2704
|
+
}
|
|
2705
|
+
function refToPath(ref) {
|
|
2706
|
+
if (typeof ref !== "string") return null;
|
|
2707
|
+
const match = /^\{(.+)\}$/.exec(ref.trim());
|
|
2708
|
+
if (!match) return null;
|
|
2709
|
+
return match[1].split(/[./]/).filter(Boolean);
|
|
2710
|
+
}
|
|
2711
|
+
function extensionObject(leaf) {
|
|
2712
|
+
const ext = leaf.extensions[NS];
|
|
2713
|
+
return isObject(ext) ? ext : {};
|
|
2714
|
+
}
|
|
2715
|
+
function modesOf(leaf) {
|
|
2716
|
+
const ext = extensionObject(leaf);
|
|
2717
|
+
return isObject(ext.modes) ? ext.modes : {};
|
|
2718
|
+
}
|
|
2719
|
+
function provenanceOf(leaf) {
|
|
2720
|
+
const ext = extensionObject(leaf);
|
|
2721
|
+
return isObject(ext.provenance) ? ext.provenance : {};
|
|
2722
|
+
}
|
|
2723
|
+
/** Shared structural checks preserve the same short-circuit/getter order in both guards. */
|
|
2724
|
+
function hasColorComponents(value) {
|
|
2725
|
+
if (!isObject(value)) return false;
|
|
2726
|
+
if (value.colorSpace !== "srgb" && value.colorSpace !== "oklch") return false;
|
|
2727
|
+
if (!Array.isArray(value.components) || value.components.length !== 3) return false;
|
|
2728
|
+
return true;
|
|
2729
|
+
}
|
|
2730
|
+
function isSolidColorValue(value) {
|
|
2731
|
+
return hasColorComponents(value) && value.alpha === 1;
|
|
2732
|
+
}
|
|
2733
|
+
function isColorLikeValue(value) {
|
|
2734
|
+
return hasColorComponents(value) && typeof value.alpha === "number" && value.alpha >= 0 && value.alpha <= 1;
|
|
2735
|
+
}
|
|
2736
|
+
function validateNoUnexpectedName(path, errors) {
|
|
2737
|
+
const joined = pathKey(path);
|
|
2738
|
+
if (joined.includes("_")) errors.push(`${joined}: token name must not contain underscores`);
|
|
2739
|
+
if (path.some((segment) => /[A-Z]/.test(segment))) errors.push(`${joined}: token name must be lowercase`);
|
|
2740
|
+
}
|
|
2741
|
+
function validateT3Path(path, errors) {
|
|
2742
|
+
const joined = pathKey(path);
|
|
2743
|
+
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`);
|
|
2744
|
+
}
|
|
2745
|
+
/** Existing scales plus the explicitly approved RFC0012 standard endpoint. */
|
|
2746
|
+
function classifyT2Step(step) {
|
|
2747
|
+
if (step === "base") return "base";
|
|
2748
|
+
if (T2_ALPHA_SET.has(step)) return "alpha";
|
|
2749
|
+
if (T2_OKLCH_SET.has(step)) return "oklch";
|
|
2750
|
+
if (step === T2_BLACK_CONTRAST_ENDPOINT.step) return "constant";
|
|
2751
|
+
return null;
|
|
2752
|
+
}
|
|
2753
|
+
/** 各 T2 步类对应的 provenance.sourceKind(血缘治理不变量)。 */
|
|
2754
|
+
const T2_STEP_SOURCE_KIND = {
|
|
2755
|
+
base: "seed-alias",
|
|
2756
|
+
alpha: "seed-derived",
|
|
2757
|
+
oklch: "design-calibrated",
|
|
2758
|
+
constant: "standard-constant"
|
|
2759
|
+
};
|
|
2760
|
+
function validateT2Provenance(leaf, seed, step, errors) {
|
|
2761
|
+
const joined = pathKey(leaf.path);
|
|
2762
|
+
const provenance = provenanceOf(leaf);
|
|
2763
|
+
if (Object.keys(provenance).length === 0) {
|
|
2764
|
+
errors.push(`${joined}: T2 token must declare $extensions.${NS}.provenance`);
|
|
2765
|
+
return;
|
|
2165
2766
|
}
|
|
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");
|
|
2767
|
+
const { sourceKind, seedRefs } = provenance;
|
|
2768
|
+
if (typeof sourceKind !== "string" || !PROVENANCE_SOURCE_KINDS.has(sourceKind)) {
|
|
2769
|
+
errors.push(`${joined}: provenance.sourceKind must be one of ${[...PROVENANCE_SOURCE_KINDS].join(", ")}`);
|
|
2770
|
+
return;
|
|
2192
2771
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
};
|
|
2772
|
+
const stepKind = classifyT2Step(step);
|
|
2773
|
+
if (stepKind && T2_STEP_SOURCE_KIND[stepKind] !== sourceKind) errors.push(`${joined}: T2 ${stepKind} token must declare provenance.sourceKind = "${T2_STEP_SOURCE_KIND[stepKind]}"`);
|
|
2774
|
+
if (SEED_LINEAGE_KINDS.has(sourceKind)) {
|
|
2775
|
+
const expected = `{qw.t1.${seed}}`;
|
|
2776
|
+
if (!Array.isArray(seedRefs) || seedRefs.length !== 1 || seedRefs[0] !== expected) errors.push(`${joined}: provenance.seedRefs must be exactly ["${expected}"]`);
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
if (Array.isArray(seedRefs) && seedRefs.length > 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must not fabricate Seed lineage`);
|
|
2780
|
+
if (typeof provenance.source !== "string" || provenance.source.length === 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must declare provenance.source`);
|
|
2781
|
+
if (typeof provenance.rationale !== "string" || provenance.rationale.length === 0) errors.push(`${joined}: provenance.sourceKind "${sourceKind}" must declare provenance.rationale`);
|
|
2782
|
+
}
|
|
2783
|
+
/** T3 角色全集(qw 路径),由 FIGMA_T3_COLOR_ROLES 投影。 */
|
|
2784
|
+
function figmaT3RolePaths() {
|
|
2785
|
+
return Object.keys(FIGMA_T3_COLOR_ROLES).map((role) => pathKey([
|
|
2786
|
+
"qw",
|
|
2787
|
+
"t3",
|
|
2788
|
+
...role.split(".")
|
|
2789
|
+
]));
|
|
2201
2790
|
}
|
|
2202
|
-
function
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
const constName = componentSuffixConstName(c);
|
|
2215
|
-
const typeName = componentSuffixTypeName(c);
|
|
2216
|
-
lines.push(`export const ${constName} = ${JSON.stringify(suffixes)} as const;`);
|
|
2217
|
-
lines.push(`export type ${typeName} = (typeof ${constName})[number];`);
|
|
2791
|
+
function validateFigmaColorTokens(doc) {
|
|
2792
|
+
const errors = [];
|
|
2793
|
+
const leaves = collectLeaves(doc).map((leaf) => ({
|
|
2794
|
+
...leaf,
|
|
2795
|
+
qwPath: qwPath(leaf.path)
|
|
2796
|
+
})).filter((leaf) => leaf.qwPath != null);
|
|
2797
|
+
const byQwPath = /* @__PURE__ */ new Map();
|
|
2798
|
+
for (const leaf of leaves) {
|
|
2799
|
+
const key = pathKey(leaf.qwPath);
|
|
2800
|
+
if (byQwPath.has(key)) errors.push(`${key}: duplicated token path`);
|
|
2801
|
+
byQwPath.set(key, leaf);
|
|
2802
|
+
validateNoUnexpectedName(leaf.qwPath, errors);
|
|
2218
2803
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2804
|
+
const t1 = leaves.filter((leaf) => leaf.qwPath[1] === "t1");
|
|
2805
|
+
const t2 = leaves.filter((leaf) => leaf.qwPath[1] === "t2");
|
|
2806
|
+
const t3 = leaves.filter((leaf) => leaf.qwPath[1] === "t3");
|
|
2807
|
+
const unknownLayers = leaves.filter((leaf) => ![
|
|
2808
|
+
"t1",
|
|
2809
|
+
"t2",
|
|
2810
|
+
"t3"
|
|
2811
|
+
].includes(leaf.qwPath[1] ?? ""));
|
|
2812
|
+
for (const leaf of unknownLayers) errors.push(`${pathKey(leaf.qwPath)}: unknown Color token layer`);
|
|
2813
|
+
const t1Keys = /* @__PURE__ */ new Set();
|
|
2814
|
+
for (const leaf of t1) {
|
|
2815
|
+
const [, , key, ...rest] = leaf.qwPath;
|
|
2816
|
+
const joined = pathKey(leaf.qwPath);
|
|
2817
|
+
if (rest.length > 0 || !key) errors.push(`${joined}: T1 token must be qw/t1/{seed}`);
|
|
2818
|
+
if (key && !T1_SET.has(key)) errors.push(`${joined}: unexpected T1 seed "${key}"`);
|
|
2819
|
+
if (!isSolidColorValue(leaf.value)) errors.push(`${joined}: T1 seed must be a solid color with alpha = 1`);
|
|
2820
|
+
if (key) t1Keys.add(key);
|
|
2226
2821
|
}
|
|
2227
|
-
|
|
2822
|
+
const missingT1 = T1_COLOR_KEYS.filter((key) => !t1Keys.has(key));
|
|
2823
|
+
const extraT1 = [...t1Keys].filter((key) => !T1_SET.has(key));
|
|
2824
|
+
if (missingT1.length) errors.push(`qw.t1: missing required seeds ${missingT1.join(", ")}`);
|
|
2825
|
+
if (extraT1.length) errors.push(`qw.t1: unexpected seeds ${extraT1.join(", ")}`);
|
|
2826
|
+
const t2BySeed = /* @__PURE__ */ new Map();
|
|
2827
|
+
for (const seed of T1_COLOR_KEYS) t2BySeed.set(seed, /* @__PURE__ */ new Set());
|
|
2828
|
+
for (const leaf of t2) {
|
|
2829
|
+
const [, , seed, step, ...rest] = leaf.qwPath;
|
|
2830
|
+
const joined = pathKey(leaf.qwPath);
|
|
2831
|
+
if (rest.length > 0 || !seed || !step) errors.push(`${joined}: T2 token must be qw/t2/{seed}/{base|alpha|oklch-step}`);
|
|
2832
|
+
if (seed && !T1_SET.has(seed)) errors.push(`${joined}: T2 seed "${seed}" must exist in T1`);
|
|
2833
|
+
const stepKind = step ? classifyT2Step(step) : null;
|
|
2834
|
+
if (stepKind === "constant") {
|
|
2835
|
+
if (seed !== "black") errors.push(`${joined}: RFC0012 endpoint is only permitted under black`);
|
|
2836
|
+
const value = leaf.value;
|
|
2837
|
+
if (!isObject(value) || value.colorSpace !== "srgb" || value.alpha !== 1 || !Array.isArray(value.components) || value.components.length !== 3 || value.components.some((channel) => channel !== 0)) errors.push(`${joined}: contrast endpoint must be the approved opaque sRGB black mathematical endpoint`);
|
|
2838
|
+
}
|
|
2839
|
+
if (step && !stepKind) errors.push(`${joined}: unsupported T2 step "${step}"`);
|
|
2840
|
+
if (!isColorLikeValue(leaf.value) && step !== "base") errors.push(`${joined}: T2 token must carry a concrete color value`);
|
|
2841
|
+
if (seed && step && T1_SET.has(seed) && stepKind) {
|
|
2842
|
+
t2BySeed.get(seed).add(step);
|
|
2843
|
+
validateT2Provenance(leaf, seed, step, errors);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
for (const seed of T1_COLOR_KEYS) {
|
|
2847
|
+
const steps = t2BySeed.get(seed);
|
|
2848
|
+
const missingAlpha = T2_ALPHA_KEYS.filter((step) => !steps.has(step));
|
|
2849
|
+
const missingOklch = T2_OKLCH_STEP_KEYS.filter((step) => !steps.has(step));
|
|
2850
|
+
const unexpected = [...steps].filter((step) => !classifyT2Step(step));
|
|
2851
|
+
if (!steps.has("base")) errors.push(`qw.t2.${seed}: missing base token`);
|
|
2852
|
+
if (missingAlpha.length) errors.push(`qw.t2.${seed}: missing alpha steps ${missingAlpha.join(", ")}`);
|
|
2853
|
+
if (missingOklch.length) errors.push(`qw.t2.${seed}: missing OKLCH steps ${missingOklch.join(", ")}`);
|
|
2854
|
+
if (unexpected.length) errors.push(`qw.t2.${seed}: unexpected steps ${unexpected.join(", ")}`);
|
|
2855
|
+
if (seed === "black" && !steps.has(T2_BLACK_CONTRAST_ENDPOINT.step)) errors.push("qw.t2.black: missing RFC0012 contrast endpoint");
|
|
2856
|
+
}
|
|
2857
|
+
const expectedT3 = new Set(figmaT3RolePaths());
|
|
2858
|
+
const actualT3 = /* @__PURE__ */ new Set();
|
|
2859
|
+
for (const leaf of t3) {
|
|
2860
|
+
const joined = pathKey(leaf.qwPath);
|
|
2861
|
+
actualT3.add(joined);
|
|
2862
|
+
validateT3Path(leaf.qwPath, errors);
|
|
2863
|
+
if (!expectedT3.has(joined)) errors.push(`${joined}: unexpected T3 role (not in the canonical Figma/local-approved set)`);
|
|
2864
|
+
const modes = modesOf(leaf);
|
|
2865
|
+
const light = refToPath(modes.light);
|
|
2866
|
+
const dark = refToPath(modes.dark);
|
|
2867
|
+
if (!light || !dark) {
|
|
2868
|
+
errors.push(`${joined}: T3 token must declare both light and dark mode references`);
|
|
2869
|
+
continue;
|
|
2870
|
+
}
|
|
2871
|
+
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`);
|
|
2872
|
+
else if (!byQwPath.has(pathKey(ref))) errors.push(`${joined}: ${modeName} mode references missing token {${pathKey(ref)}}`);
|
|
2873
|
+
}
|
|
2874
|
+
const missingT3 = [...expectedT3].filter((role) => !actualT3.has(role));
|
|
2875
|
+
if (missingT3.length) errors.push(`qw.t3: missing required roles ${missingT3.join(", ")}`);
|
|
2876
|
+
return {
|
|
2877
|
+
ok: errors.length === 0,
|
|
2878
|
+
errors,
|
|
2879
|
+
counts: {
|
|
2880
|
+
t1: t1.length,
|
|
2881
|
+
t2: t2.length,
|
|
2882
|
+
t3: t3.length
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
function assertFigmaColorTokens(doc) {
|
|
2887
|
+
const result = validateFigmaColorTokens(doc);
|
|
2888
|
+
if (!result.ok) throw new Error(`Invalid Figma Color tokens:\n${result.errors.map((error) => `- ${error}`).join("\n")}`);
|
|
2228
2889
|
}
|
|
2229
2890
|
//#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,
|
|
2891
|
+
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
2892
|
|
|
2232
2893
|
//# sourceMappingURL=index.js.map
|