dsh-theme-palette 1.8.2 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/client.js +83 -4
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@
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
+
7
23
  ### 1.8.2
8
24
  **暖砂字体体系修正**(用户反馈“和其他两套不一样,尤其字体颜色”):
9
25
 
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.2'
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
 
@@ -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
  *
@@ -716,6 +740,17 @@ window.__ModuleLoader__.load({
716
740
  light: withAlpha(r.bluish['50'], 0.58),
717
741
  dark: withAlpha(r.bluish['750'], 0.45),
718
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
+ },
719
754
  '--dsw-alias-menu-group-header-fill': {
720
755
  // 用 60 而不是 50:50 现在是「深色模式正文」的色度,拿去当浅色菜单头会偏艳
721
756
  light: withAlpha(r.bluish['60'], 0.94),
@@ -936,6 +971,19 @@ window.__ModuleLoader__.load({
936
971
  light: mixHex(ramps.deepseek['500'], ramps.bluish['50'], 0.05),
937
972
  dark: mixHex(ramps.deepseek['400'], ramps.bluish['900'], 0.07),
938
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
+ }
939
987
  /* ---- 语法高亮(1.8.2)----
940
988
  * 宿主渲染器用 Shiki 的 `css-variables` 模式(className="shiki css-variables",
941
989
  * 样式走 var(--shiki-*)),所以覆盖这些变量有效,不需要 !important。
@@ -1028,6 +1076,8 @@ window.__ModuleLoader__.load({
1028
1076
  * 命中 ui-layout 的 `data-rightbar-col`(稳定语义钩子,不是哈希类名)。
1029
1077
  * 全屏右栏时退回画布色 —— 那时右栏就是整块内容区,染成侧栏色会像只剩一条侧栏。
1030
1078
  */
1079
+ // 说明:本插件不额外处理 forced-colors —— 我们改的 color/background-color/border-color
1080
+ // 都在 UA 强制色列表内,系统高对比模式下会被自动接管,无需(也不应)用 forced-color-adjust 对抗。
1031
1081
  const RIGHTBAR_CSS = [
1032
1082
  '[data-rightbar-col]{background:var(--dshtp-rightbar-fill,var(--dsw-alias-bg-base))}',
1033
1083
  '[data-rightbar-fullscreen] [data-rightbar-col]{background:var(--dsw-alias-bg-base)}',
@@ -1474,6 +1524,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1474
1524
  explainLink: '链接已为可读性压暗:{ratio}:1(目标 {target}:1)',
1475
1525
  preview: '预览',
1476
1526
  sample: '试试示例',
1527
+ copyDiag: '复制诊断',
1477
1528
  contrast: '对比度',
1478
1529
  contrastSub: '实测 / 目标(按承载面;※ 不计硬门·△ 表面受限)',
1479
1530
  cMain: '正文',
@@ -1527,6 +1578,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1527
1578
  explainLink: '連結已為可讀性壓暗:{ratio}:1(目標 {target}:1)',
1528
1579
  preview: '預覽',
1529
1580
  sample: '試試示例',
1581
+ copyDiag: '複製診斷',
1530
1582
  contrast: '對比度',
1531
1583
  contrastSub: '實測 / 目標(依承載面;※ 不計硬門·△ 表面受限)',
1532
1584
  cMain: '正文',
@@ -1580,6 +1632,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1580
1632
  explainLink: 'Link darkened for readability: {ratio}:1 (target {target}:1)',
1581
1633
  preview: 'Preview',
1582
1634
  sample: 'Try a sample',
1635
+ copyDiag: 'Copy diagnostics',
1583
1636
  contrast: 'Contrast',
1584
1637
  contrastSub: 'measured / target per surface (※ not gated · △ surface-limited)',
1585
1638
  cMain: 'Body',
@@ -1633,6 +1686,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1633
1686
  explainLink: 'リンクを可読性のため暗くしました:{ratio}:1(目標 {target}:1)',
1634
1687
  preview: 'プレビュー',
1635
1688
  sample: 'サンプルを試す',
1689
+ copyDiag: '診断をコピー',
1636
1690
  contrast: 'コントラスト',
1637
1691
  contrastSub: '実測 / 目標(面ごと;※ 判定外・△ 面の上限)',
1638
1692
  cMain: '本文',
@@ -1735,6 +1789,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1735
1789
  const [copyState, setCopyState] = React.useState(null)
1736
1790
  const [advanced, setAdvanced] = React.useState(false)
1737
1791
  const [matrixOpen, setMatrixOpen] = React.useState(false)
1792
+ const [diagState, setDiagState] = React.useState(null)
1738
1793
  // 升级提示按版本只出现一次
1739
1794
  const noticePending = React.useSyncExternalStore(props.subscribeNotice, props.getNoticePending)
1740
1795
  // HEX 输入需要「草稿」态:非法值不能写进主题,但用户还在输入时也不该被立刻回滚。
@@ -1907,6 +1962,20 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1907
1962
  onClick: () => setMatrixOpen(!matrixOpen),
1908
1963
  }, (matrixOpen ? '▾ ' : '▸ ') + txt.contrast),
1909
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)),
1910
1979
  matrixOpen && (() => {
1911
1980
  const report = props.getContrastReport()
1912
1981
  return h(
@@ -1914,7 +1983,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
1914
1983
  { id: 'dsh-tp-matrix-body' },
1915
1984
  h('thead', null, h('tr', null,
1916
1985
  h('th', { scope: 'col' }, ''),
1917
- 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 ? '※' : ''))))),
1918
1987
  h('tbody', null, report.rows.map((row) => h('tr', { key: row.role },
1919
1988
  h('th', { scope: 'row' }, roleLabel[row.role] || row.role),
1920
1989
  row.cells.map((cell) => {
@@ -2123,7 +2192,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
2123
2192
 
2124
2193
  // 开发期接口:palette-dev.mjs 通过它拿到同一套引擎(避免「报表好看、上线不一样」)
2125
2194
  if (DEV_OPT_IN && typeof window !== 'undefined' && typeof window.__DSH_THEME_PALETTE_DEV__ === 'function') {
2126
- window.__DSH_THEME_PALETTE_DEV__({ buildTokens, SCHEMES, SIDEBAR_MODES, TEXT_MODES, RIGHTBAR_MODES, SCROLL_MODES, CONTROL_MODES, MD_MODES, HC_MODES, contrast, toOklch, fromOklch, 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 })
2127
2196
  }
2128
2197
 
2129
2198
  return {
@@ -2429,6 +2498,13 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
2429
2498
  linkTarget: floors(hc).link,
2430
2499
  }
2431
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
+ })
2432
2508
  const previewConfigCode = (code) => {
2433
2509
  const cfg = decodeConfig(code)
2434
2510
  return cfg ? { ok: true, config: cfg } : { ok: false }
@@ -2452,7 +2528,9 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
2452
2528
  const fgHex = role.id === 'link'
2453
2529
  ? t['--dsw-alias-link'][sf.mode]
2454
2530
  : t['--dsw-static-neutral-bluish-' + role.step[sf.mode]][sf.mode]
2455
- const bg = t['--dsw-static-neutral-bluish-' + sf.id][sf.mode]
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]
2456
2534
  const ratio = contrast(fgHex, bg)
2457
2535
  const claim = sf.mode === 'light' ? F[role.id] : (role.id === 'link' ? F.linkDark : F[role.id])
2458
2536
  const ceiling = contrast(sf.mode === 'light' ? '#000000' : '#ffffff', bg)
@@ -2627,6 +2705,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
2627
2705
  previewConfigCode,
2628
2706
  getSampleCode,
2629
2707
  getContrastReport,
2708
+ getDiagnostics,
2630
2709
  subscribeNotice,
2631
2710
  getNoticePending,
2632
2711
  dismissNotice,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-theme-palette",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
5
5
  "type": "module",
6
6
  "main": "index.js",