dsh-theme-palette 1.8.1 → 1.8.3
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/CHANGELOG.md +40 -0
- package/client.js +133 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,46 @@
|
|
|
4
4
|
|
|
5
5
|
## 版本一览
|
|
6
6
|
|
|
7
|
+
### 1.8.3
|
|
8
|
+
补齐 token 覆盖缺口(按宿主 118 个 alias/specific 做传递闭包统计:76% 跟随;余下 28 个逐项判断该不该跟):
|
|
9
|
+
|
|
10
|
+
- **深色菜单**:`--dsw-specific-menu` 宿主带字面量兜底(深 `#303136f0`),只改 `menu-surface-fill` 不够,
|
|
11
|
+
深色菜单仍是冷灰 → 现在按配色给(浅 50/深 850,alpha 0.94)
|
|
12
|
+
- **品牌新色**:`--dsw-alias-brand-primary-new-…` 浅色写死 `#4176e6` 宿主蓝 → 两模式都归到强调色
|
|
13
|
+
- **文件 diff 温度调和**:6 个 `file-diff-*` 保留绿/红语义,色相往配色拉 16–22°、压低饱和;
|
|
14
|
+
断言「新增仍是绿色系、删除仍是红色系」+ 正文对 diff 底 ≥4.5
|
|
15
|
+
- **对比度矩阵加菜单面**:半透明面按画布合成后给出参考值,标 `※`(不计硬门),
|
|
16
|
+
UI 与 `palette-dev` 共用同一份 `CONTRAST_SURFACES`(含 token 面)
|
|
17
|
+
- **诊断导出**:矩阵区新增「复制诊断」,输出 settings + 对比度矩阵 JSON,便于提 issue
|
|
18
|
+
- **forced-colors**:核实我们改的都是 UA 强制色属性,系统高对比模式会自动接管,
|
|
19
|
+
因此**不加** `forced-color-adjust: none` 对抗(代码内已注明理由)
|
|
20
|
+
- 契约测试补 `--dsw-specific-menu` / `--dsw-alias-brand-primary-new-…` / `--dsw-alias-file-diff-added-bg`
|
|
21
|
+
- 明确不跟:`bg-mask-*`(遮罩)、`border-inverted*`(反色边框)、onboarding(一次性引导)——刻意保留宿主原值
|
|
22
|
+
|
|
23
|
+
### 1.8.2
|
|
24
|
+
**暖砂字体体系修正**(用户反馈“和其他两套不一样,尤其字体颜色”):
|
|
25
|
+
|
|
26
|
+
- **根因 1:深色正文字色度没有按色域归一**。`paperC` 是绝对值,×2.4 后暖砂请求 130%、靛蓝 166% 色域上限,
|
|
27
|
+
实际色度被裁成 0.0244 / 0.0115 / 0.0303——暖砂深正文几乎是靛蓝的两倍黄。现在三套统一 `paperC=0.0053`
|
|
28
|
+
(标准档实际 ≈0.0127),浅/深正文色度比降到 1.21 / 1.10
|
|
29
|
+
- **根因 2:暖砂是唯一「深色面 60°、正文 84°」的双色相预置**。深色面橙棕、正文黄绿,色相分裂 24°;
|
|
30
|
+
现在 `paper` 角色跟随 `darkHue`,并把暖砂 `hue` 84→72、`darkHue` 60→66,全套色相收敛到 12° 内
|
|
31
|
+
- 新增断言:深正文色相偏离 `darkHue` ≤12°、三套浅色正文色度比 ≤1.25、深色 ≤1.5
|
|
32
|
+
- 新增 `tools/preview-typography.mjs`:三套 × 明暗并排渲染同一段文字,发版前目检
|
|
33
|
+
|
|
34
|
+
语法高亮跟随配色(1.8.1 里“未做”的那条,前提已实测):
|
|
35
|
+
|
|
36
|
+
- **实测依据**:宿主渲染器用 Shiki 的 `css-variables` 模式(`class="shiki css-variables"`,
|
|
37
|
+
`style="background-color:var(--shiki-background)"`),token 颜色走 CSS 变量,
|
|
38
|
+
所以覆盖 `--shiki-token-*` 生效,不需要 `!important`
|
|
39
|
+
- **色相策略**:从强调色相取 3 个色族(强调 / +150° 冷族 / -60° 暖族),
|
|
40
|
+
同族靠明度分常/表达;每个 token 对代码块底求解到 **≥4.6:1**,四族最小 ΔE **≥10**
|
|
41
|
+
- 覆盖 9 类 token(keyword / function / constant / parameter / string / string-expression /
|
|
42
|
+
comment / punctuation / link)+ `--shiki-background` / `--shiki-foreground`
|
|
43
|
+
- **md=off 时不输出**任何 shiki token,交回宿主原色
|
|
44
|
+
- 新增 `tools/preview-code.mjs`:用生产 token 渲染一段真实代码,发版前肉眼确认
|
|
45
|
+
- 断言:9 类 token 对比度 ≥4.6、四族 ΔE ≥10、shiki 背景与代码块一致、md=off 无输出
|
|
46
|
+
|
|
7
47
|
### 1.8.1
|
|
8
48
|
Markdown 配色丰富化(非加色相,而是加层级 + 修代码块底):
|
|
9
49
|
|
package/client.js
CHANGED
|
@@ -27,7 +27,7 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const React = require('react')
|
|
28
28
|
const h = React.createElement
|
|
29
29
|
|
|
30
|
-
const PLUGIN_VERSION = '1.8.
|
|
30
|
+
const PLUGIN_VERSION = '1.8.3'
|
|
31
31
|
const STORE_KEY = 'dsh-theme-palette.v1'
|
|
32
32
|
const OVERRIDE_SOURCE = 'dsh-theme-palette'
|
|
33
33
|
|
|
@@ -107,22 +107,22 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
{ id: 'mono', label: '黑白', desc: '无色调 · 默认', tinted: false },
|
|
108
108
|
{
|
|
109
109
|
id: 'warm', label: '暖砂', desc: '米白 + 棕调', tinted: true,
|
|
110
|
-
hue:
|
|
111
|
-
darkC: 0.014, darkLift: 0.026, paperC: 0.
|
|
110
|
+
hue: 72, darkHue: 66, lightC: 0.030, midC: 0.010, textC: 0.004,
|
|
111
|
+
darkC: 0.014, darkLift: 0.026, paperC: 0.0053, // 深正文按色域归一:×2.4 后 ≈0.0127
|
|
112
112
|
accentHue: 60, accentC: 0.101, accentL: 0.58, // 标准色 #A9683E
|
|
113
113
|
accentScale: 1.0,
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
id: 'indigo', label: '靛蓝', desc: '冷白 + 蓝调', tinted: true,
|
|
117
117
|
hue: 266, lightC: 0.020, midC: 0.010, textC: 0.004,
|
|
118
|
-
darkC: 0.028, darkLift: 0.032, paperC: 0.
|
|
118
|
+
darkC: 0.028, darkLift: 0.032, paperC: 0.0053, // 同上:三套统一,避免被色域裁成两倍差
|
|
119
119
|
accentHue: 259, accentC: 0.107, accentL: 0.5108, // 标准色 #3F66A3
|
|
120
120
|
accentScale: 1.0,
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
id: 'moss', label: '苔绿', desc: '浅灰绿 + 绿调', tinted: true,
|
|
124
124
|
hue: 162, lightC: 0.016, midC: 0.010, textC: 0.004,
|
|
125
|
-
darkC: 0.015, darkLift: 0.042, paperC: 0.
|
|
125
|
+
darkC: 0.015, darkLift: 0.042, paperC: 0.0053, // 同上
|
|
126
126
|
accentHue: 139, accentC: 0.096, accentL: 0.5335, // 标准色 #4F7A42
|
|
127
127
|
accentScale: 1.0,
|
|
128
128
|
},
|
|
@@ -240,6 +240,10 @@ window.__ModuleLoader__.load({
|
|
|
240
240
|
{ id: '875', mode: 'dark', scope: true },
|
|
241
241
|
{ id: '850', mode: 'dark', scope: true },
|
|
242
242
|
{ id: '800', mode: 'dark', scope: true },
|
|
243
|
+
// 菜单是半透明面,实际颜色取决于 backdrop;这里按"合成在画布上"给出参考值,
|
|
244
|
+
// 标 scope:false(不计硬门),并在 UI 用 ※ 注明依赖 backdrop
|
|
245
|
+
{ id: 'menuL', label: 'menu', mode: 'light', scope: false, token: '--dsw-specific-menu' },
|
|
246
|
+
{ id: 'menuD', label: 'menu', mode: 'dark', scope: false, token: '--dsw-specific-menu' },
|
|
243
247
|
]
|
|
244
248
|
const CONTRAST_ROLES = [
|
|
245
249
|
{ id: 'main', step: { light: '1000', dark: '50' } },
|
|
@@ -437,6 +441,26 @@ window.__ModuleLoader__.load({
|
|
|
437
441
|
return hex + Math.round(clamp(alpha, 0, 1) * 255).toString(16).padStart(2, '0')
|
|
438
442
|
}
|
|
439
443
|
|
|
444
|
+
/** 8 位 hex(带 alpha)按「覆盖在 bg 上」合成为不透明色;不带 alpha 时原样返回。 */
|
|
445
|
+
function flatten(hex, bgHex) {
|
|
446
|
+
if (typeof hex !== 'string' || hex.length !== 9) return hex
|
|
447
|
+
const fg = hexToRgb(hex.slice(0, 7))
|
|
448
|
+
const bg = hexToRgb(bgHex)
|
|
449
|
+
const a = parseInt(hex.slice(7, 9), 16) / 255
|
|
450
|
+
return rgbToHex(fg.map((v, i) => v * a + bg[i] * (1 - a)))
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* 语义色温度调和:保留色相为主(绿仍是绿、红仍是红),只把色相往配色的暖/冷方向
|
|
455
|
+
* 拉一小段、并压低饱和度。用于 file diff 这种大面积色块——不能整块换成主题色,
|
|
456
|
+
* 那会让「新增/删除」失去语义。
|
|
457
|
+
*/
|
|
458
|
+
function harmonize(hex, targetHue, pull, chromaScale) {
|
|
459
|
+
const [L, C, h] = toOklch(hex)
|
|
460
|
+
const dh = ((targetHue - h + 540) % 360) - 180
|
|
461
|
+
return fromOklch(L, C * chromaScale, h + dh * pull)
|
|
462
|
+
}
|
|
463
|
+
|
|
440
464
|
/**
|
|
441
465
|
* 区块底色微调:在 OKLab 里给亮度加 delta,同时按需削减色度。
|
|
442
466
|
*
|
|
@@ -495,9 +519,10 @@ window.__ModuleLoader__.load({
|
|
|
495
519
|
// 换成 60°(橙棕)才是咖啡色。浅色侧仍是 88°(米白)。
|
|
496
520
|
// 文字档(含深色正文 paper)跟随「文字」这一项自己的色相;预设没有 textHue,行为不变。
|
|
497
521
|
const tintRole = role === 'text' || role === 'paper'
|
|
522
|
+
// 深色角色(含深色正文 paper)统一走 darkHue,避免「深色面橙棕、正文黄绿」的双色相
|
|
498
523
|
const hue = tintRole && scheme.textHue !== undefined
|
|
499
524
|
? scheme.textHue
|
|
500
|
-
: (role === 'dark' && scheme.darkHue !== undefined ? scheme.darkHue : scheme.hue)
|
|
525
|
+
: ((role === 'dark' || role === 'paper') && scheme.darkHue !== undefined ? scheme.darkHue : scheme.hue)
|
|
501
526
|
bluish[step] = fromOklch(clamp(compressL(lifted), 0, 0.995), roleChroma(role, weight, scheme, scales), hue)
|
|
502
527
|
}
|
|
503
528
|
const neutral = {}
|
|
@@ -715,6 +740,17 @@ window.__ModuleLoader__.load({
|
|
|
715
740
|
light: withAlpha(r.bluish['50'], 0.58),
|
|
716
741
|
dark: withAlpha(r.bluish['750'], 0.45),
|
|
717
742
|
},
|
|
743
|
+
// 宿主 `--dsw-specific-menu` 自带字面量兜底(浅 #f8f9faf0 / 深 #303136f0),
|
|
744
|
+
// 只改 menu-surface-fill 不够:深色菜单实际用的是它,会留着冷灰。这里一并对齐。
|
|
745
|
+
'--dsw-specific-menu': {
|
|
746
|
+
light: withAlpha(r.bluish['50'], 0.94),
|
|
747
|
+
dark: withAlpha(r.bluish['850'], 0.94),
|
|
748
|
+
},
|
|
749
|
+
// 浅色写死 #4176e6(宿主蓝),深色已引用 deepseek-450;两模式都归到强调色
|
|
750
|
+
'--dsw-alias-brand-primary-new-colorprimary-new-color': {
|
|
751
|
+
light: r.deepseek['500'],
|
|
752
|
+
dark: r.deepseek['400'],
|
|
753
|
+
},
|
|
718
754
|
'--dsw-alias-menu-group-header-fill': {
|
|
719
755
|
// 用 60 而不是 50:50 现在是「深色模式正文」的色度,拿去当浅色菜单头会偏艳
|
|
720
756
|
light: withAlpha(r.bluish['60'], 0.94),
|
|
@@ -935,6 +971,63 @@ window.__ModuleLoader__.load({
|
|
|
935
971
|
light: mixHex(ramps.deepseek['500'], ramps.bluish['50'], 0.05),
|
|
936
972
|
dark: mixHex(ramps.deepseek['400'], ramps.bluish['900'], 0.07),
|
|
937
973
|
}
|
|
974
|
+
/* ---- 文件 diff:宿主是固定冷绿/冷红的大面积色块,暖砂/苔绿下最显眼。
|
|
975
|
+
* 只做「温度调和」:保留绿=新增、红=删除的语义色相,往配色色相拉 16–22° 并压低饱和。 */
|
|
976
|
+
if (params.tinted) {
|
|
977
|
+
const diffHue = params.hue
|
|
978
|
+
const area = (hex) => harmonize(hex, diffHue, 0.16, 0.90)
|
|
979
|
+
const mark = (hex) => harmonize(hex, diffHue, 0.22, 0.95)
|
|
980
|
+
pairs['--dsw-alias-file-diff-added-bg'] = { light: area('#e6f4e7'), dark: area('#1f3124') }
|
|
981
|
+
pairs['--dsw-alias-file-diff-added-gutter'] = { light: area('#edf7ed'), dark: area('#132016') }
|
|
982
|
+
pairs['--dsw-alias-file-diff-added-marker'] = { light: mark('#01a241'), dark: mark('#41c977') }
|
|
983
|
+
pairs['--dsw-alias-file-diff-deleted-bg'] = { light: area('#fce6e2'), dark: area('#3c1f1b') }
|
|
984
|
+
pairs['--dsw-alias-file-diff-deleted-gutter'] = { light: area('#fdece9'), dark: area('#28130e') }
|
|
985
|
+
pairs['--dsw-alias-file-diff-deleted-marker'] = { light: mark('#ba2723'), dark: mark('#fa423e') }
|
|
986
|
+
}
|
|
987
|
+
/* ---- 语法高亮(1.8.2)----
|
|
988
|
+
* 宿主渲染器用 Shiki 的 `css-variables` 模式(className="shiki css-variables",
|
|
989
|
+
* 样式走 var(--shiki-*)),所以覆盖这些变量有效,不需要 !important。
|
|
990
|
+
*
|
|
991
|
+
* 色相策略:不引入与配色无关的固定色,而是从强调色相出发取 3 个色族
|
|
992
|
+
* (强调 / +150° 的冷族 / -55° 的暖族),再用明度与色度拉开可辨度;
|
|
993
|
+
* 每个 token 都必须对代码块底达到 4.6:1,否则沿远离背景的方向调整明度。
|
|
994
|
+
*/
|
|
995
|
+
if (params.tinted && mdMode === 'on') {
|
|
996
|
+
const baseHue = params.accentHue === undefined ? scheme.hue : params.accentHue
|
|
997
|
+
const pickL = (bg) => (contrast('#ffffff', bg) >= contrast('#000000', bg) ? 0.80 : 0.45)
|
|
998
|
+
const dirOf = (bg) => (contrast('#ffffff', bg) >= contrast('#000000', bg) ? 1 : -1)
|
|
999
|
+
const ink = (bg, hue, chroma, startL, target) => {
|
|
1000
|
+
const dir = dirOf(bg)
|
|
1001
|
+
let L = startL
|
|
1002
|
+
for (let i = 0; i < 160; i++) {
|
|
1003
|
+
const c = fromOklch(clamp(L, 0.02, 0.99), chroma, hue)
|
|
1004
|
+
if (contrast(c, bg) >= target) return c
|
|
1005
|
+
L += dir * 0.004
|
|
1006
|
+
}
|
|
1007
|
+
return fromOklch(clamp(L, 0.02, 0.99), chroma, hue)
|
|
1008
|
+
}
|
|
1009
|
+
// 每个色族自带目标对比度:同族靠明度分"常/表达",异族靠色相差 55–150°
|
|
1010
|
+
const families = {
|
|
1011
|
+
keyword: [baseHue, 0.15, 4.6],
|
|
1012
|
+
function: [baseHue + 70, 0.12, 5.0],
|
|
1013
|
+
constant: [baseHue - 60, 0.13, 4.8],
|
|
1014
|
+
parameter: [baseHue + 30, 0.10, 5.6],
|
|
1015
|
+
string: [baseHue + 150, 0.10, 4.8],
|
|
1016
|
+
'string-expression': [baseHue + 150, 0.085, 5.8],
|
|
1017
|
+
comment: [baseHue, 0.015, 4.6],
|
|
1018
|
+
punctuation: [baseHue, 0.02, 6.0],
|
|
1019
|
+
}
|
|
1020
|
+
for (const [name, [hue, chroma, target]] of Object.entries(families)) {
|
|
1021
|
+
const key = '--shiki-token-' + name
|
|
1022
|
+
pairs[key] = {
|
|
1023
|
+
light: ink(codeFill.light, hue, chroma, pickL(codeFill.light), target),
|
|
1024
|
+
dark: ink(codeFill.dark, hue, chroma, pickL(codeFill.dark), target),
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
pairs['--shiki-token-link'] = { light: lightLink(ramps, false), dark: ramps.deepseek['400'] }
|
|
1028
|
+
pairs['--shiki-background'] = codeFill
|
|
1029
|
+
pairs['--shiki-foreground'] = { light: ramps.bluish['1000'], dark: ramps.bluish['50'] }
|
|
1030
|
+
}
|
|
938
1031
|
const scroll = scrollbarTokens(ramps, params, scrollMode)
|
|
939
1032
|
for (const [name, pair] of Object.entries(scroll)) pairs[name] = pair
|
|
940
1033
|
// 开关/勾选/单选的共用支点:brand-primary 本来是近黑/近白,改成强调色。
|
|
@@ -983,6 +1076,8 @@ window.__ModuleLoader__.load({
|
|
|
983
1076
|
* 命中 ui-layout 的 `data-rightbar-col`(稳定语义钩子,不是哈希类名)。
|
|
984
1077
|
* 全屏右栏时退回画布色 —— 那时右栏就是整块内容区,染成侧栏色会像只剩一条侧栏。
|
|
985
1078
|
*/
|
|
1079
|
+
// 说明:本插件不额外处理 forced-colors —— 我们改的 color/background-color/border-color
|
|
1080
|
+
// 都在 UA 强制色列表内,系统高对比模式下会被自动接管,无需(也不应)用 forced-color-adjust 对抗。
|
|
986
1081
|
const RIGHTBAR_CSS = [
|
|
987
1082
|
'[data-rightbar-col]{background:var(--dshtp-rightbar-fill,var(--dsw-alias-bg-base))}',
|
|
988
1083
|
'[data-rightbar-fullscreen] [data-rightbar-col]{background:var(--dsw-alias-bg-base)}',
|
|
@@ -1429,6 +1524,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1429
1524
|
explainLink: '链接已为可读性压暗:{ratio}:1(目标 {target}:1)',
|
|
1430
1525
|
preview: '预览',
|
|
1431
1526
|
sample: '试试示例',
|
|
1527
|
+
copyDiag: '复制诊断',
|
|
1432
1528
|
contrast: '对比度',
|
|
1433
1529
|
contrastSub: '实测 / 目标(按承载面;※ 不计硬门·△ 表面受限)',
|
|
1434
1530
|
cMain: '正文',
|
|
@@ -1482,6 +1578,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1482
1578
|
explainLink: '連結已為可讀性壓暗:{ratio}:1(目標 {target}:1)',
|
|
1483
1579
|
preview: '預覽',
|
|
1484
1580
|
sample: '試試示例',
|
|
1581
|
+
copyDiag: '複製診斷',
|
|
1485
1582
|
contrast: '對比度',
|
|
1486
1583
|
contrastSub: '實測 / 目標(依承載面;※ 不計硬門·△ 表面受限)',
|
|
1487
1584
|
cMain: '正文',
|
|
@@ -1535,6 +1632,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1535
1632
|
explainLink: 'Link darkened for readability: {ratio}:1 (target {target}:1)',
|
|
1536
1633
|
preview: 'Preview',
|
|
1537
1634
|
sample: 'Try a sample',
|
|
1635
|
+
copyDiag: 'Copy diagnostics',
|
|
1538
1636
|
contrast: 'Contrast',
|
|
1539
1637
|
contrastSub: 'measured / target per surface (※ not gated · △ surface-limited)',
|
|
1540
1638
|
cMain: 'Body',
|
|
@@ -1588,6 +1686,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1588
1686
|
explainLink: 'リンクを可読性のため暗くしました:{ratio}:1(目標 {target}:1)',
|
|
1589
1687
|
preview: 'プレビュー',
|
|
1590
1688
|
sample: 'サンプルを試す',
|
|
1689
|
+
copyDiag: '診断をコピー',
|
|
1591
1690
|
contrast: 'コントラスト',
|
|
1592
1691
|
contrastSub: '実測 / 目標(面ごと;※ 判定外・△ 面の上限)',
|
|
1593
1692
|
cMain: '本文',
|
|
@@ -1690,6 +1789,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1690
1789
|
const [copyState, setCopyState] = React.useState(null)
|
|
1691
1790
|
const [advanced, setAdvanced] = React.useState(false)
|
|
1692
1791
|
const [matrixOpen, setMatrixOpen] = React.useState(false)
|
|
1792
|
+
const [diagState, setDiagState] = React.useState(null)
|
|
1693
1793
|
// 升级提示按版本只出现一次
|
|
1694
1794
|
const noticePending = React.useSyncExternalStore(props.subscribeNotice, props.getNoticePending)
|
|
1695
1795
|
// HEX 输入需要「草稿」态:非法值不能写进主题,但用户还在输入时也不该被立刻回滚。
|
|
@@ -1862,6 +1962,20 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1862
1962
|
onClick: () => setMatrixOpen(!matrixOpen),
|
|
1863
1963
|
}, (matrixOpen ? '▾ ' : '▸ ') + txt.contrast),
|
|
1864
1964
|
h('span', { className: 'dsh-tp-matrix-sub' }, txt.contrastSub),
|
|
1965
|
+
h('div', { className: 'dsh-tp-field-row' },
|
|
1966
|
+
h('button', {
|
|
1967
|
+
type: 'button',
|
|
1968
|
+
className: 'dsh-tp-mini',
|
|
1969
|
+
onClick: async () => {
|
|
1970
|
+
try {
|
|
1971
|
+
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') throw new Error('no clipboard')
|
|
1972
|
+
await navigator.clipboard.writeText(JSON.stringify(props.getDiagnostics(), null, 2))
|
|
1973
|
+
setDiagState('copied')
|
|
1974
|
+
} catch (err) { setDiagState('fail') }
|
|
1975
|
+
},
|
|
1976
|
+
}, txt.copyDiag),
|
|
1977
|
+
diagState && h('span', { className: 'dsh-tp-note', role: 'status', 'aria-live': 'polite' },
|
|
1978
|
+
diagState === 'copied' ? txt.copied : txt.copyFail)),
|
|
1865
1979
|
matrixOpen && (() => {
|
|
1866
1980
|
const report = props.getContrastReport()
|
|
1867
1981
|
return h(
|
|
@@ -1869,7 +1983,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1869
1983
|
{ id: 'dsh-tp-matrix-body' },
|
|
1870
1984
|
h('thead', null, h('tr', null,
|
|
1871
1985
|
h('th', { scope: 'col' }, ''),
|
|
1872
|
-
CONTRAST_SURFACES.map((sf) => h('th', { key: sf.id, scope: 'col' }, sf.id + (sf.scope === false ? '※' : ''))))),
|
|
1986
|
+
CONTRAST_SURFACES.map((sf) => h('th', { key: sf.id, scope: 'col' }, (sf.label || sf.id) + (sf.scope === false ? '※' : ''))))),
|
|
1873
1987
|
h('tbody', null, report.rows.map((row) => h('tr', { key: row.role },
|
|
1874
1988
|
h('th', { scope: 'row' }, roleLabel[row.role] || row.role),
|
|
1875
1989
|
row.cells.map((cell) => {
|
|
@@ -2078,7 +2192,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
2078
2192
|
|
|
2079
2193
|
// 开发期接口:palette-dev.mjs 通过它拿到同一套引擎(避免「报表好看、上线不一样」)
|
|
2080
2194
|
if (DEV_OPT_IN && typeof window !== 'undefined' && typeof window.__DSH_THEME_PALETTE_DEV__ === 'function') {
|
|
2081
|
-
window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, RIGHTBAR_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, HC_MODES, contrast, toOklch, rampsFor, floors, encodeConfig, decodeConfig, customScheme, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, settingsCss: SETTINGS_CSS, cardColors })
|
|
2195
|
+
window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, RIGHTBAR_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, HC_MODES, contrast, toOklch, fromOklch, flatten, rampsFor, floors, encodeConfig, decodeConfig, customScheme, readCustom, writeCustom, CONTRAST_SURFACES, CONTRAST_ROLES, settingsCss: SETTINGS_CSS, cardColors })
|
|
2082
2196
|
}
|
|
2083
2197
|
|
|
2084
2198
|
return {
|
|
@@ -2384,6 +2498,13 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
2384
2498
|
linkTarget: floors(hc).link,
|
|
2385
2499
|
}
|
|
2386
2500
|
}
|
|
2501
|
+
/** 诊断导出:把当前配色与对比度矩阵打成可粘贴的 JSON(提 issue 用)。 */
|
|
2502
|
+
const getDiagnostics = () => ({
|
|
2503
|
+
plugin: 'dsh-theme-palette',
|
|
2504
|
+
version: PLUGIN_VERSION,
|
|
2505
|
+
settings: { palette: current, sidebar: sidebarMode, text: textMode, rightbar: rightbarMode, scroll: scrollMode, control: controlMode, md: mdMode, hc: hcMode, lang: langPref },
|
|
2506
|
+
report: getContrastReport(),
|
|
2507
|
+
})
|
|
2387
2508
|
const previewConfigCode = (code) => {
|
|
2388
2509
|
const cfg = decodeConfig(code)
|
|
2389
2510
|
return cfg ? { ok: true, config: cfg } : { ok: false }
|
|
@@ -2407,7 +2528,9 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
2407
2528
|
const fgHex = role.id === 'link'
|
|
2408
2529
|
? t['--dsw-alias-link'][sf.mode]
|
|
2409
2530
|
: t['--dsw-static-neutral-bluish-' + role.step[sf.mode]][sf.mode]
|
|
2410
|
-
const bg =
|
|
2531
|
+
const bg = sf.token
|
|
2532
|
+
? flatten(t[sf.token][sf.mode], t['--dsw-static-neutral-bluish-' + (sf.mode === 'light' ? '00' : '950')][sf.mode])
|
|
2533
|
+
: t['--dsw-static-neutral-bluish-' + sf.id][sf.mode]
|
|
2411
2534
|
const ratio = contrast(fgHex, bg)
|
|
2412
2535
|
const claim = sf.mode === 'light' ? F[role.id] : (role.id === 'link' ? F.linkDark : F[role.id])
|
|
2413
2536
|
const ceiling = contrast(sf.mode === 'light' ? '#000000' : '#ffffff', bg)
|
|
@@ -2582,6 +2705,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
2582
2705
|
previewConfigCode,
|
|
2583
2706
|
getSampleCode,
|
|
2584
2707
|
getContrastReport,
|
|
2708
|
+
getDiagnostics,
|
|
2585
2709
|
subscribeNotice,
|
|
2586
2710
|
getNoticePending,
|
|
2587
2711
|
dismissNotice,
|