@ash-qw/dsh-theme-prts 2.5.5 → 2.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ash-qw/dsh-theme-prts",
3
- "version": "2.5.5",
3
+ "version": "2.6.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web and the official desktop client",
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=20.9.0",
72
- "dsh": "0.2.1-rc.2 || 0.2.1-rc.3"
72
+ "dsh": "0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.5"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@playwright/test": "1.55.1",
@@ -185,13 +185,22 @@ function mountBodyTargetFrame(document, window, operation) {
185
185
  const find=node=>{
186
186
  if(activeModal && !activeModal.contains(node))return null
187
187
  if (!node?.closest || node.closest('[data-prts-workspace-actions],[data-prts-session-actions]')) return null
188
- const schedule=node.closest('[data-tool="schedule_create"]')
189
- const scheduleOpen=schedule && [...schedule.children].find(child=>child.tagName==='BUTTON' && hasClassSuffix(child,'_cardOpen') && !child.matches(':disabled,[aria-disabled="true"]'))
188
+ const container=node.closest('[data-presented-file],[data-changed-files],[data-tool="schedule_create"]')
190
189
  const button=node.closest('button')
191
- // The transparent hit layer targets the card; nested actions keep their own frame.
192
- const card=node.closest('[data-presented-file],[data-changed-files]') || (scheduleOpen && (!button || button===scheduleOpen)?schedule:null)
190
+ let cardTarget=null
191
+ if(container){
192
+ const action=node.closest('button,a[href],[role="button"],[role="link"],summary,input:not([type="hidden"]),select,textarea')
193
+ const nested=action && action!==container && container.contains(action)
194
+ // Disabled controls must not fall back to their enclosing card.
195
+ if(node.closest(':disabled,[aria-disabled="true"],[inert]'))return null
196
+ const hitLayer=nested && action.tagName==='BUTTON' && (
197
+ container.matches('[data-presented-file]') && hasClassSuffix(action,'_cardPreview') ||
198
+ action.parentElement===container && container.matches('[data-tool="schedule_create"]') && hasClassSuffix(action,'_cardOpen'))
199
+ const openable=!container.matches('[data-tool="schedule_create"]') || [...container.children].some(child=>child.tagName==='BUTTON' && hasClassSuffix(child,'_cardOpen') && !child.matches(':disabled,[aria-disabled="true"]'))
200
+ cardTarget=nested && !hitLayer ? action : openable ? container : null
201
+ }
193
202
  const pendingAction=operation.contains(button) && isPendingInteractionAction(button) ? button : null
194
- const candidate=card || pendingAction || node.closest(selector)
203
+ const candidate=cardTarget || pendingAction || node.closest(selector)
195
204
  return candidate && !candidate.matches(':disabled,[aria-disabled="true"]') && !candidate.closest('[inert]') ? candidate : null
196
205
  }
197
206
  const measure=(box)=>{bounds=box || target.getBoundingClientRect();targetArm=7+Math.max(0,Math.min(1,(Math.min(bounds.width,bounds.height)-36)/36))*5;boundsDirty=false}
@@ -223,7 +223,11 @@ export function applyPrtsPlugin(ctx, environment) {
223
223
  persistAndApply(updatePreferenceValue(preferences, key, value))
224
224
  },
225
225
  onPreferencePreview(key, value) {
226
- if (key === 'cornerColor') { document.documentElement.style.setProperty('--prts-corner-color', String(value)); return }
226
+ if (key === 'cornerColor' || key === 'cornerColorLight') {
227
+ const properties = preferences.cornerColorLinked ? ['--prts-corner-color-light', '--prts-corner-color-dark'] : [key === 'cornerColorLight' ? '--prts-corner-color-light' : '--prts-corner-color-dark']
228
+ for (const property of properties) document.documentElement.style.setProperty(property, String(value))
229
+ return
230
+ }
227
231
  if (key === 'particleTraversalSpeed') {
228
232
  particleField.update(updatePreferenceValue(preferences, key, value))
229
233
  return
@@ -28,11 +28,15 @@ export function createNativePreferences({ storage, onChange }) {
28
28
  migrationAttempted = true
29
29
  pending = { ...legacy, ...pending }
30
30
  }
31
- // Materialize the old shared sidebar values once, so later left-side edits
32
- // cannot change a right-side preference that was never written by the Host.
31
+ // Materialize legacy shared values before either side can be edited independently.
33
32
  if (s.value?.preferencesInitialized && writable(s)) {
34
- for (const key of ['rightSidebarOpacity', 'rightSidebarBlur']) {
35
- if (!Object.hasOwn(s.value, key) && !Object.hasOwn(pending, key) && !Object.hasOwn(inFlight, key) && !Object.hasOwn(failed, key)) pending[key] = host[key]
33
+ if (s.value.cornerColorSource == null && s.value.cornerColorLinked === true) {
34
+ for (const key of ['cornerColor', 'cornerCustomColor']) {
35
+ if (s.value[key] !== host[key] && !Object.hasOwn(pending, key) && !Object.hasOwn(inFlight, key) && !Object.hasOwn(failed, key)) pending[key] = host[key]
36
+ }
37
+ }
38
+ for (const key of ['rightSidebarOpacity', 'rightSidebarBlur', 'cornerColorLight', 'cornerCustomColorLight', 'cornerColorSource']) {
39
+ if (s.value[key] == null && !Object.hasOwn(pending, key) && !Object.hasOwn(inFlight, key) && !Object.hasOwn(failed, key)) pending[key] = host[key]
36
40
  }
37
41
  }
38
42
  current = normalizePreferences({ ...host, ...inFlight, ...pending })
@@ -39,8 +39,12 @@ export const DEFAULT_PREFERENCES = Object.freeze({
39
39
  bakerBlur: 8,
40
40
  classicUserBlur: 8,
41
41
  cursor: 'prts',
42
+ cornerColorLinked: false,
43
+ cornerColorSource: 'dark',
42
44
  cornerColor: '#f0c800',
43
45
  cornerCustomColor: '#f0c800',
46
+ cornerColorLight: '#f0c800',
47
+ cornerCustomColorLight: '#f0c800',
44
48
  conversationParticleDensity: 'sparse',
45
49
  heroParticleDensity: 'light',
46
50
  particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
@@ -107,7 +111,7 @@ export const PREFERENCE_GROUPS = Object.freeze({
107
111
  background: Object.freeze(['texture', 'backgroundMode', 'backgroundDim', 'heroContrast', 'sidebarOpacity', 'rightSidebarOpacity', 'railOpacity', 'bakerOpacity', 'classicUserOpacity', 'sidebarBlur', 'rightSidebarBlur', 'railBlur', 'bakerBlur', 'classicUserBlur']),
108
112
  particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
109
113
  material: Object.freeze(['glass', 'composerGlass']),
110
- accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor', 'cornerColor', 'cornerCustomColor']),
114
+ accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor', 'cornerColorLinked', 'cornerColorSource', 'cornerColor', 'cornerCustomColor', 'cornerColorLight', 'cornerCustomColorLight']),
111
115
  aiTools: Object.freeze(['aiToolsEnabled']),
112
116
  navigation: Object.freeze(['railDefaultHidden', 'sessionPreview', 'previewOngoing', 'previewTools', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']),
113
117
  conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle', 'bakerGroupAvatars', 'bakerLoading', 'conversationEmblem']),
@@ -231,8 +235,12 @@ export function normalizePreferences(value) {
231
235
  backgroundDim: typeof input.backgroundDim === 'number' && Number.isFinite(input.backgroundDim) ? Math.min(.9, Math.max(0, input.backgroundDim)) : .45,
232
236
  composerGlass: booleanOr(input.composerGlass, DEFAULT_PREFERENCES.composerGlass),
233
237
  bootAnimation: booleanOr(input.bootAnimation, DEFAULT_PREFERENCES.bootAnimation),
238
+ cornerColorLinked: booleanOr(input.cornerColorLinked, false),
239
+ cornerColorSource: input.cornerColorSource === 'light' ? 'light' : 'dark',
234
240
  cornerCustomColor: colorOr(input.cornerCustomColor, DEFAULT_PREFERENCES.cornerCustomColor),
235
241
  cornerColor: colorOr(input.cornerColor, DEFAULT_PREFERENCES.cornerColor),
242
+ cornerColorLight: colorOr(input.cornerColorLight, colorOr(input.cornerColor, DEFAULT_PREFERENCES.cornerColor)),
243
+ cornerCustomColorLight: colorOr(input.cornerCustomColorLight, colorOr(input.cornerCustomColor, DEFAULT_PREFERENCES.cornerCustomColor)),
236
244
  cursor: input.cursor === 'system' ? 'system' : DEFAULT_PREFERENCES.cursor,
237
245
  conversationParticleDensity: enumOr(legacyDensities.conversation, PARTICLE_DENSITIES, particlePreset.conversationParticleDensity),
238
246
  heroParticleDensity: enumOr(legacyDensities.hero, PARTICLE_DENSITIES, particlePreset.heroParticleDensity),
@@ -263,6 +271,11 @@ export function normalizePreferences(value) {
263
271
  bakerGroupAvatars: booleanOr(input.bakerGroupAvatars, DEFAULT_PREFERENCES.bakerGroupAvatars),
264
272
  conversationStyle: enumOr(input.conversationStyle, CONVERSATION_STYLES, DEFAULT_PREFERENCES.conversationStyle),
265
273
  }
274
+ // Preserve the active color from the interim shared-palette format on upgrade.
275
+ if (input.cornerColorSource == null && input.cornerColorLinked === true) {
276
+ normalized.cornerColor = colorOr(input.cornerColorShared, normalized.cornerColor)
277
+ normalized.cornerCustomColor = colorOr(input.cornerCustomColorShared, normalized.cornerCustomColor)
278
+ }
266
279
  normalized.preset = matchedVisualPreset(normalized)
267
280
  return normalized
268
281
  }
@@ -287,9 +300,16 @@ export function updatePreferenceValue(value, key, nextValue) {
287
300
  if (key === 'preset') return applyVisualPreset(value, nextValue)
288
301
  if (key === 'particleDetail') return applyParticleDetail(value, nextValue)
289
302
  const current = normalizePreferences(value)
290
- if (key === 'cornerCustomColor') {
291
- const color = colorOr(nextValue, current.cornerCustomColor)
292
- return normalizePreferences({ ...current, cornerColor: color, cornerCustomColor: color })
303
+ if (key === 'cornerColorLink') {
304
+ const enabled = nextValue === 'light' || nextValue === 'dark'
305
+ return normalizePreferences({ ...current, cornerColorLinked: enabled,
306
+ ...(enabled ? { cornerColorSource: nextValue } : {}) })
307
+ }
308
+ if (['cornerColor', 'cornerColorLight', 'cornerCustomColor', 'cornerCustomColorLight'].includes(key)) {
309
+ const color = colorOr(nextValue, current[key])
310
+ const custom = key.startsWith('cornerCustom')
311
+ const colorKey = custom ? key.replace('cornerCustomColor', 'cornerColor') : key
312
+ return normalizePreferences({ ...current, [colorKey]: color, ...(custom ? { [key]: color } : {}) })
293
313
  }
294
314
  if (key === 'sessionFlowPalette') {
295
315
  const palette = enumOr(nextValue, SESSION_FLOW_PALETTE_IDS, current.sessionFlowPalette)
@@ -19,7 +19,8 @@ const ROOT_ATTRIBUTES = [
19
19
  'data-prts-background-mode',
20
20
  ]
21
21
  const SESSION_FLOW_COLOR_PROPERTIES = {
22
- '--prts-corner-color': 'cornerColor',
22
+ '--prts-corner-color-dark': 'cornerColor',
23
+ '--prts-corner-color-light': 'cornerColorLight',
23
24
  '--prts-session-flow-back': 'sessionFlowColorBack',
24
25
  '--prts-session-flow-core': 'sessionFlowColorCore',
25
26
  '--prts-session-flow-front': 'sessionFlowColorFront',
@@ -766,7 +767,7 @@ export function createThemeController({ document, window, cssText, service, curs
766
767
  root.dataset.prtsSessionGlow = preferences.sessionGlow ? 'on' : 'off'
767
768
  root.dataset.prtsSessionFlowPalette = preferences.sessionFlowPalette
768
769
  root.dataset.prtsSessionFlowSpeed = String(preferences.sessionFlowSpeed)
769
- for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences[key])
770
+ for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences.cornerColorLinked && (key === 'cornerColor' || key === 'cornerColorLight') ? preferences[preferences.cornerColorSource === 'light' ? 'cornerColorLight' : 'cornerColor'] : preferences[key])
770
771
  root.dataset.prtsConversationStyle = preferences.conversationStyle
771
772
  root.toggleAttribute('data-prts-baker-loading', preferences.bakerLoading === true)
772
773
  root.dataset.prtsBackgroundMode = preferences.backgroundMode
@@ -14,7 +14,8 @@ const ROOT_ATTRIBUTES = [
14
14
  'data-prts-conversation-style',
15
15
  ]
16
16
  const SESSION_FLOW_COLOR_PROPERTIES = {
17
- '--prts-corner-color': 'cornerColor',
17
+ '--prts-corner-color-dark': 'cornerColor',
18
+ '--prts-corner-color-light': 'cornerColorLight',
18
19
  '--prts-session-flow-back': 'sessionFlowColorBack',
19
20
  '--prts-session-flow-core': 'sessionFlowColorCore',
20
21
  '--prts-session-flow-front': 'sessionFlowColorFront',
@@ -124,7 +125,7 @@ export function createThemeController({ document, window, cssText, service }) {
124
125
  root.dataset.prtsSessionGlow = preferences.sessionGlow ? 'on' : 'off'
125
126
  root.dataset.prtsSessionFlowPalette = preferences.sessionFlowPalette
126
127
  root.dataset.prtsSessionFlowSpeed = String(preferences.sessionFlowSpeed)
127
- for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences[key])
128
+ for (const [property, key] of Object.entries(SESSION_FLOW_COLOR_PROPERTIES)) root.style.setProperty(property, preferences.cornerColorLinked && (key === 'cornerColor' || key === 'cornerColorLight') ? preferences[preferences.cornerColorSource === 'light' ? 'cornerColorLight' : 'cornerColor'] : preferences[key])
128
129
  root.dataset.prtsConversationStyle = preferences.conversationStyle
129
130
  refresh()
130
131
  },
@@ -171,7 +171,7 @@ export function themeSettingsMarkup() {
171
171
  <section data-prts-settings-common data-prts-settings-section="interaction" role="tabpanel" id="prts-page-interaction" aria-labelledby="prts-tab-interaction" tabindex="0" hidden>
172
172
  <div data-prts-settings-common-grid>
173
173
  ${COMMON_CONTROLS.filter(([key]) => ['cursor', 'railDefaultHidden', 'motion'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
174
- <div data-prts-setting-row="cornerColor"><span><strong>交互提示框颜色</strong><small data-prts-setting-note>部分按钮和卡片的四角提示颜色,危险操作保持红色。</small></span><div data-prts-corner-presets role="group" aria-label="交互提示框预设颜色">${[['黄色','#f0c800'],['白色','#ffffff'],['青色','#65ddb0'],['蓝色','#6eb7f3'],['紫色','#b487ef']].map(([name,color]) => `<button type="button" data-prts-corner-preset="${color}" aria-label="${name}" title="${name}" aria-pressed="false" style="--swatch:${color}"><i aria-hidden="true"></i></button>`).join('')}<button type="button" data-prts-corner-custom title="选择自定义颜色"><i data-prts-corner-preview aria-hidden="true"></i><span>自定义</span></button><input type="color" data-prts-corner-color aria-label="交互提示框颜色" value="#f0c800" hidden></div></div>
174
+ <div data-prts-setting-row="cornerColor"><span><span data-prts-corner-heading><strong>交互提示框颜色</strong><span data-prts-corner-modes role="group" aria-label="编辑配色模式"><button type="button" data-prts-corner-mode="light" aria-pressed="false">☀ 日间</button><button type="button" data-prts-corner-mode="dark" aria-pressed="false">☾ 夜间</button></span><button type="button" data-prts-corner-sync role="switch" aria-checked="false" title="开启后,日间和夜间共用颜色"><span>日夜共用</span><i aria-hidden="true"></i></button></span><small data-prts-setting-note>部分按钮和卡片的四角提示颜色,危险操作保持红色。</small><small data-prts-setting-note>开启后,日夜都使用当前选定模式的配色,修改也保存到该模式;关闭后各用各的,另一套配色不变。</small></span><div data-prts-corner-presets role="group" aria-label="交互提示框预设颜色">${[['黄色','#f0c800'],['白色','#ffffff'],['青色','#65ddb0'],['蓝色','#6eb7f3'],['紫色','#b487ef']].map(([name,color]) => `<button type="button" data-prts-corner-preset="${color}" aria-label="${name}" title="${name}" aria-pressed="false" style="--swatch:${color}"><i aria-hidden="true"></i></button>`).join('')}<span data-prts-corner-picker><button type="button" data-prts-corner-custom aria-expanded="false" title="选择自定义颜色"><i data-prts-corner-preview aria-hidden="true"></i><span>自定义</span></button><input type="color" data-prts-corner-color aria-label="交互提示框颜色" value="#f0c800" tabindex="-1"><span data-prts-color-editor hidden role="group" aria-label="自定义提示框颜色"><span data-prts-color-editor-title>自定义颜色</span><span data-prts-color-sample></span><label data-prts-color-hex-label>HEX<input data-prts-color-hex aria-label="HEX 颜色" maxlength="7" spellcheck="false"></label><label data-prts-color-channel><span>R</span><input type="range" min="0" max="255" step="1" data-prts-color-channel-index="0" aria-label="R 通道"><output>0</output></label><label data-prts-color-channel><span>G</span><input type="range" min="0" max="255" step="1" data-prts-color-channel-index="1" aria-label="G 通道"><output>0</output></label><label data-prts-color-channel><span>B</span><input type="range" min="0" max="255" step="1" data-prts-color-channel-index="2" aria-label="B 通道"><output>0</output></label><span data-prts-color-actions><button type="button" data-prts-color-cancel>取消</button><button type="button" data-prts-color-apply>应用颜色</button></span></span></span></div></div>
175
175
  </div>
176
176
  </section>
177
177
  <section data-prts-settings-section="ai-tools" data-prts-ai-tools role="tabpanel" id="prts-page-ai-tools" aria-labelledby="prts-tab-ai-tools" tabindex="0" hidden>
@@ -285,6 +285,35 @@ export function createThemeSettingsOverlay({
285
285
  const nativeModal = typeof panel?.showModal === 'function' && typeof panel?.close === 'function'
286
286
  let isOpen = false
287
287
  let previousFocus
288
+ let colorDraft = '#f0c800'
289
+ function closeColorEditor(restoreFocus = false) {
290
+ const editor = panel?.querySelector('[data-prts-color-editor]')
291
+ if (!editor || editor.hidden) return
292
+ editor.hidden = true
293
+ panel.querySelector('[data-prts-corner-custom]').setAttribute('aria-expanded', 'false')
294
+ onPreferencePreview(cornerKey(), currentPreferences[cornerKey()])
295
+ if (restoreFocus) panel.querySelector('[data-prts-corner-custom]').focus()
296
+ }
297
+ function renderColorDraft() {
298
+ const editor = panel.querySelector('[data-prts-color-editor]')
299
+ editor.querySelector('[data-prts-color-sample]').style.background = colorDraft
300
+ const hex = editor.querySelector('[data-prts-color-hex]')
301
+ hex.value = colorDraft.toUpperCase()
302
+ hex.removeAttribute('aria-invalid')
303
+ const channels = [1, 3, 5].map(start => parseInt(colorDraft.slice(start, start + 2), 16))
304
+ for (const slider of editor.querySelectorAll('[data-prts-color-channel-index]')) {
305
+ const index = Number(slider.dataset.prtsColorChannelIndex)
306
+ slider.value = channels[index]
307
+ slider.nextElementSibling.value = channels[index]
308
+ const end = [...channels], start = [...channels]
309
+ start[index] = 0; end[index] = 255
310
+ slider.style.background = 'linear-gradient(to right,rgb(' + start.join(',') + '),rgb(' + end.join(',') + '))'
311
+ }
312
+ }
313
+ let cornerEditMode = 'dark'
314
+ const cornerMode = () => currentPreferences?.cornerColorLinked ? currentPreferences.cornerColorSource : cornerEditMode
315
+ const cornerKey = () => cornerMode() === 'light' ? 'cornerColorLight' : 'cornerColor'
316
+ const cornerCustomKey = () => cornerMode() === 'light' ? 'cornerCustomColorLight' : 'cornerCustomColor'
288
317
  let currentPreferences
289
318
  let currentStatus
290
319
  let currentPersistence = { phase: 'idle', revision: 0 }
@@ -342,6 +371,8 @@ export function createThemeSettingsOverlay({
342
371
  const value = Boolean(next)
343
372
  if (value === isOpen) return isOpen
344
373
  if (!value) {
374
+ closeColorEditor()
375
+ if (currentPreferences) onPreferencePreview(cornerKey(), currentPreferences[cornerKey()])
345
376
  commitParticleTraversalSpeed()
346
377
  commitSessionFlowSpeed()
347
378
  commitSessionFlowColors()
@@ -351,6 +382,8 @@ export function createThemeSettingsOverlay({
351
382
  document.documentElement.toggleAttribute('data-prts-settings-open', isOpen)
352
383
  panel.setAttribute('aria-hidden', String(!isOpen))
353
384
  if (isOpen) {
385
+ cornerEditMode = document.documentElement.getAttribute('data-prts-scheme') === 'light' ? 'light' : 'dark'
386
+ renderState()
354
387
  previousFocus = document.activeElement
355
388
  onOpen()
356
389
  showPanel()
@@ -532,10 +565,18 @@ export function createThemeSettingsOverlay({
532
565
 
533
566
  function renderState() {
534
567
  if (!panel || !currentPreferences) return
568
+ panel.querySelector('[data-prts-corner-modes]').hidden = currentPreferences.cornerColorLinked
569
+ panel.querySelector('[data-prts-corner-sync]').setAttribute('aria-checked', String(currentPreferences.cornerColorLinked))
570
+ for (const mode of panel.querySelectorAll('[data-prts-corner-mode]')) mode.setAttribute('aria-pressed', String(mode.dataset.prtsCornerMode === cornerEditMode))
535
571
  const cornerColor = panel.querySelector('[data-prts-corner-color]')
536
- if (cornerColor) cornerColor.value = currentPreferences.cornerCustomColor
537
- panel.querySelector('[data-prts-corner-preview]')?.style.setProperty('--swatch', currentPreferences.cornerCustomColor)
538
- for (const preset of panel.querySelectorAll('[data-prts-corner-preset]')) preset.setAttribute('aria-pressed', String(preset.dataset.prtsCornerPreset === currentPreferences.cornerColor))
572
+ if (cornerColor) cornerColor.value = currentPreferences[cornerCustomKey()]
573
+ panel.querySelector('[data-prts-corner-preview]')?.style.setProperty('--swatch', currentPreferences[cornerCustomKey()])
574
+ const customButton = panel.querySelector('[data-prts-corner-custom]')
575
+ const customApplied = currentPreferences[cornerKey()] === currentPreferences[cornerCustomKey()]
576
+ customButton.setAttribute('aria-pressed', String(customApplied))
577
+ customButton.title = customApplied ? '自定义颜色已应用,点击编辑' : '选择自定义颜色'
578
+ customButton.querySelector('span').textContent = '自定义'
579
+ for (const preset of panel.querySelectorAll('[data-prts-corner-preset]')) preset.setAttribute('aria-pressed', String(preset.dataset.prtsCornerPreset === currentPreferences[cornerKey()]))
539
580
  for (const toggle of panel.querySelectorAll('[data-prts-setting-switch]')) {
540
581
  const key = toggle.dataset.prtsSettingSwitch
541
582
  const enabled = key === 'cursor' ? currentPreferences.cursor === 'prts'
@@ -628,10 +669,29 @@ export function createThemeSettingsOverlay({
628
669
 
629
670
  function onPanelClick(event) {
630
671
  if (event.target === panel) { setOpen(false); return }
672
+ if (!event.target?.closest?.('[data-prts-corner-picker]')) closeColorEditor()
631
673
  const target = event.target?.closest?.('button')
632
674
  if (!target) return
633
- if (target.hasAttribute('data-prts-corner-custom')) { panel.querySelector('[data-prts-corner-color]').click(); return }
634
- if (target.hasAttribute('data-prts-corner-preset')) { onPreferenceChange('cornerColor', target.dataset.prtsCornerPreset); return }
675
+ if (target.hasAttribute('data-prts-corner-mode')) { onPreferencePreview(cornerKey(), currentPreferences[cornerKey()]); cornerEditMode = target.dataset.prtsCornerMode; renderState(); return }
676
+ if (target.hasAttribute('data-prts-corner-sync')) { if (currentPreferences.cornerColorLinked) cornerEditMode = currentPreferences.cornerColorSource; onPreferenceChange('cornerColorLink', currentPreferences.cornerColorLinked ? false : cornerEditMode); return }
677
+ if (target.hasAttribute('data-prts-color-cancel')) { closeColorEditor(true); return }
678
+ if (target.hasAttribute('data-prts-color-apply')) {
679
+ if (panel.querySelector('[data-prts-color-hex]').getAttribute('aria-invalid') === 'true') return
680
+ onPreferenceChange(cornerCustomKey(), colorDraft); closeColorEditor(true); return
681
+ }
682
+ if (target.hasAttribute('data-prts-corner-custom')) {
683
+ const editor = panel.querySelector('[data-prts-color-editor]')
684
+ if (!editor.hidden) { closeColorEditor(); return }
685
+ colorDraft = currentPreferences[cornerCustomKey()]
686
+ renderColorDraft(); editor.hidden = false
687
+ editor.removeAttribute('data-open-below')
688
+ const scroller = panel.querySelector('[data-prts-theme-settings-scroll]')
689
+ if (editor.getBoundingClientRect().top < (scroller?.getBoundingClientRect().top ?? 0)) editor.setAttribute('data-open-below', '')
690
+ target.setAttribute('aria-expanded', 'true')
691
+ editor.querySelector('[data-prts-color-hex]').focus()
692
+ return
693
+ }
694
+ if (target.hasAttribute('data-prts-corner-preset')) { onPreferenceChange(cornerKey(), target.dataset.prtsCornerPreset); return }
635
695
  if (target.hasAttribute('data-prts-settings-jump')) {
636
696
  selectSection(target.dataset.prtsSettingsJump)
637
697
  return
@@ -670,7 +730,20 @@ export function createThemeSettingsOverlay({
670
730
 
671
731
  function onPanelInput(event) {
672
732
  const target = event.target
673
- if (target?.matches?.('[data-prts-corner-color]')) { onPreferencePreview('cornerColor', target.value); return }
733
+ if (target?.matches?.('[data-prts-color-channel-index], [data-prts-color-hex]')) {
734
+ if (target.hasAttribute('data-prts-color-hex')) {
735
+ const value = target.value.trim()
736
+ if (!/^#?[0-9a-f]{6}$/i.test(value)) { target.setAttribute('aria-invalid', 'true'); return }
737
+ colorDraft = (value.startsWith('#') ? value : '#' + value).toLowerCase()
738
+ } else {
739
+ const channels = [...panel.querySelectorAll('[data-prts-color-channel-index]')].map(input => Number(input.value).toString(16).padStart(2, '0'))
740
+ colorDraft = '#' + channels.join('')
741
+ }
742
+ renderColorDraft()
743
+ onPreferencePreview(cornerKey(), colorDraft)
744
+ return
745
+ }
746
+ if (target?.matches?.('[data-prts-corner-color]')) { onPreferencePreview(cornerKey(), target.value); return }
674
747
  if (target?.matches?.('[data-prts-preview-reply-limit]')) { onPreferenceChange('previewReplyLimit', Number(target.value)); return }
675
748
  if (target?.matches?.('input[data-prts-setting-color]')) {
676
749
  const key = target.dataset.prtsSettingKey
@@ -713,7 +786,7 @@ export function createThemeSettingsOverlay({
713
786
  selectSection(tabs[next].dataset.prtsSettingsJump, true)
714
787
  return
715
788
  }
716
- if (event.key === 'Escape') { event.preventDefault(); setOpen(false); return }
789
+ if (event.key === 'Escape') { event.preventDefault(); if (!panel.querySelector('[data-prts-color-editor]').hidden) { event.stopPropagation(); closeColorEditor(true) } else setOpen(false); return }
717
790
  if (nativeModal || event.key !== 'Tab') return
718
791
  const nodes = focusable()
719
792
  if (!nodes.length) return
@@ -733,7 +806,7 @@ export function createThemeSettingsOverlay({
733
806
  const renderMediaState = () => renderState()
734
807
  const commitFromControl = event => {
735
808
  const target = event.target
736
- if (target?.matches?.('[data-prts-corner-color]')) { if (event.type === 'change') onPreferenceChange('cornerCustomColor', target.value); return }
809
+ if (target?.matches?.('[data-prts-corner-color]')) { if (event.type === 'change') onPreferenceChange(cornerCustomKey(), target.value); return }
737
810
  if (target?.matches?.('input[data-prts-setting-color]')) {
738
811
  if (event.type === 'change' || event.type === 'focusout') commitSessionFlowColor(target.dataset.prtsSettingKey)
739
812
  return
package/src/index.js CHANGED
@@ -12,9 +12,10 @@ export const PACKAGE_ID = '@ash-qw/dsh-theme-prts'
12
12
  export const THEME_PLUGIN_ID = 'dsh-theme-prts'
13
13
  export const inject = ['tools', 'attachments', 'connection', 'webServer']
14
14
 
15
+ // Leave new palette fields absent until normalization can inherit the legacy color.
15
16
  export const Config = Schema.object(Object.fromEntries([
16
17
  ...Object.entries(DEFAULT_PREFERENCES).map(([key, value]) => [key,
17
- (typeof value === 'boolean' ? Schema.boolean() : typeof value === 'number' ? Schema.number() : Schema.string()).default(value).volatile(),
18
+ (typeof value === 'boolean' ? Schema.boolean() : typeof value === 'number' ? Schema.number() : Schema.string()).default(['cornerColorLight', 'cornerCustomColorLight', 'cornerColorSource'].includes(key) ? undefined : value).volatile(),
18
19
  ]),
19
20
  ['preferencesInitialized', Schema.boolean().default(false).volatile()],
20
21
  ]))
@@ -1043,5 +1043,55 @@ html[data-dsh-prts] [data-prts-corner-presets] { display:flex;flex-wrap:wrap;gap
1043
1043
  & > button[aria-pressed="true"]::after { content:"";position:absolute;width:8px;height:4px;border-left:2px solid #172025;border-bottom:2px solid #172025;transform:translateY(-1px) rotate(-45deg); }
1044
1044
  & > button:focus-visible { outline:2px solid var(--prts-ink);outline-offset:2px; }
1045
1045
  }
1046
- html[data-dsh-prts] [data-prts-corner-presets] > [data-prts-corner-custom] { display:flex;align-items:center;gap:8px;width:auto;height:32px;margin-left:8px;padding:5px 10px;border:1px solid var(--prts-line-strong);border-radius:4px;background:var(--prts-panel-raised);color:var(--prts-ink);font:12px/1 var(--prts-body);white-space:nowrap; }
1047
- html[data-dsh-prts] [data-prts-corner-presets] > [data-prts-corner-custom]:is(:hover,:focus-visible) { border-color:var(--prts-ink);background:color-mix(in srgb,var(--prts-ink) 8%,var(--prts-panel-raised)); }
1046
+ html[data-dsh-prts] [data-prts-corner-presets] [data-prts-corner-custom] { display:flex;align-items:center;gap:8px;width:auto;height:32px;margin-left:8px;padding:5px 10px;border:1px solid var(--prts-line-strong);border-radius:4px;background:var(--prts-panel-raised);color:var(--prts-ink);font:12px/1 var(--prts-body);white-space:nowrap; }
1047
+ html[data-dsh-prts] [data-prts-corner-presets] [data-prts-corner-custom]:is(:hover,:focus-visible) { border-color:var(--prts-ink);background:color-mix(in srgb,var(--prts-ink) 8%,var(--prts-panel-raised)); }
1048
+
1049
+
1050
+ /* Resolve palette in CSS so app scheme changes never require pointer work. */
1051
+ html[data-dsh-prts] { --prts-corner-color: var(--prts-corner-color-dark, #f0c800); }
1052
+ html[data-dsh-prts][data-prts-scheme="light"] { --prts-corner-color: var(--prts-corner-color-light, #f0c800); }
1053
+ [data-prts-corner-heading] { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
1054
+ [data-prts-corner-modes] { display: inline-flex; padding: 2px; gap: 2px; border: 1px solid var(--prts-line-strong); border-radius: 6px; }
1055
+ [data-prts-corner-modes] button { padding: 4px 7px; border: 0; border-radius: 3px; background: transparent; color: inherit; font-size: 11px; opacity: .6; cursor: pointer; }
1056
+ [data-prts-corner-modes] button[aria-pressed="true"] { background: color-mix(in srgb, currentColor 12%, transparent); opacity: 1; }
1057
+ [data-prts-corner-picker] { position: relative; display: inline-flex; }
1058
+ [data-prts-corner-picker] input[type="color"] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
1059
+ html[data-dsh-prts] [data-prts-setting-row="cornerColor"] > span { flex-basis: 100%; }
1060
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-heading] { display: flex; align-items: center; gap: 12px; }
1061
+ html[data-dsh-prts] [data-prts-corner-heading] strong { font: 550 var(--prts-settings-text)/1.5 var(--prts-body); }
1062
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-modes] { display: inline-flex; }
1063
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-modes] button { font: 11px/1.4 var(--prts-body); }
1064
+
1065
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] { margin-left: auto; padding: 4px 7px; border: 0; border-radius: 4px; background: transparent; color: var(--prts-muted); font: 11px/1.4 var(--prts-body); cursor: pointer; }
1066
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync]:hover { color: var(--prts-ink); background: var(--prts-panel-raised); }
1067
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor] { position: absolute; z-index: 10; right: 0; bottom: calc(100% + 10px); width: 252px; padding: 16px; display: grid; gap: 12px; border: 1px solid var(--prts-line-strong); border-radius: 10px; background: var(--prts-panel-raised); color: var(--prts-ink); box-shadow: 0 12px 36px #0005; cursor: auto; }
1068
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor][hidden] { display: none; }
1069
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor-title] { font: 550 12px/1.4 var(--prts-body); }
1070
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-sample] { display: block; height: 44px; border-radius: 6px; box-shadow: inset 0 0 0 1px #8884; }
1071
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-hex-label] { display: flex; align-items: center; gap: 12px; font: 11px/1.5 var(--prts-mono); color: var(--prts-muted); }
1072
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-hex] { min-width: 0; width: 100%; padding: 6px 8px; border: 1px solid var(--prts-line-strong); border-radius: 4px; background: var(--prts-canvas); color: var(--prts-ink); font: 12px/1.4 var(--prts-mono); }
1073
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-hex][aria-invalid="true"] { border-color: var(--prts-red); }
1074
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-channel] { display: grid; grid-template-columns: 12px 1fr 25px; gap: 10px; align-items: center; font: 11px/1.5 var(--prts-mono); color: var(--prts-muted); }
1075
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-channel-index] { appearance: none; width: 100%; min-width: 0; height: 8px; margin: 0; padding: 0; border: 0; border-radius: 8px; cursor: pointer; }
1076
+ html[data-dsh-prts] [data-prts-color-channel-index]::-webkit-slider-thumb { appearance: none; width: 14px; height: 14px; border: 2px solid white; border-radius: 50%; background: #283036; box-shadow: 0 1px 4px #0008; }
1077
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-actions] { display: flex; justify-content: flex-end; gap: 8px; padding-top: 3px; }
1078
+ html[data-dsh-prts] [data-prts-color-actions] button { padding: 6px 10px; border: 1px solid var(--prts-line-strong); border-radius: 4px; color: var(--prts-ink); background: transparent; font: 12px/1.4 var(--prts-body); cursor: pointer; }
1079
+ html[data-dsh-prts] [data-prts-color-actions] button:hover { background: color-mix(in srgb,var(--prts-ink) 10%,transparent); }
1080
+ html[data-dsh-prts] [data-prts-color-actions] [data-prts-color-apply] { background: var(--prts-ink); color: var(--prts-canvas); }
1081
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] { display: flex; flex-direction: column; align-items: stretch; gap: 12px; }
1082
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] > span { width: 100%; }
1083
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-presets] { justify-content: flex-start; }
1084
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor] { max-width: calc(100vw - 72px); max-height: 310px; overflow-y: auto; }
1085
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-color-editor][data-open-below] { bottom: auto; top: calc(100% + 10px); }
1086
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] { flex-wrap: nowrap; }
1087
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cornerColor"] > span { flex: none; width: auto; }
1088
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-modes][hidden] { display: none; }
1089
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
1090
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] i { display: block; position: relative; width: 28px; height: 16px; border-radius: 10px; background: var(--prts-line-strong); }
1091
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync] i::after { content: ""; position: absolute; left: 3px; top: 3px; width: 10px; height: 10px; border-radius: 50%; background: var(--prts-ink); }
1092
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync][aria-checked="true"] i { background: var(--prts-yellow); }
1093
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-corner-sync][aria-checked="true"] i::after { transform: translateX(12px); background: var(--prts-canvas); }
1094
+ html[data-dsh-prts] [data-prts-corner-presets] [data-prts-corner-custom][aria-pressed="true"] { border-color: var(--prts-ink); background: color-mix(in srgb,var(--prts-ink) 10%,var(--prts-panel-raised)); }
1095
+
1096
+ html[data-dsh-prts] [data-prts-corner-custom] [data-prts-corner-preview] { position: relative; }
1097
+ html[data-dsh-prts] [data-prts-corner-custom][aria-pressed="true"] [data-prts-corner-preview]::after { content: ""; position: absolute; left: 4px; top: 5px; width: 8px; height: 4px; border-left: 2px solid #172025; border-bottom: 2px solid #172025; transform: rotate(-45deg); }