@ash-qw/dsh-theme-prts 1.0.4 → 1.0.6
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 +16 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +4 -4
- package/THIRD_PARTY_NOTICES.zh-CN.md +4 -4
- package/lib/client.js +226 -45
- package/package.json +1 -1
- package/src/assets/cursors/animation.json +83 -1
- package/src/assets/cursors/prts-link-hold.png +0 -0
- package/src/assets/cursors/prts-link-press-00.png +0 -0
- package/src/assets/cursors/prts-link-press-01.png +0 -0
- package/src/assets/cursors/prts-link-press-02.png +0 -0
- package/src/assets/cursors/prts-link-press-03.png +0 -0
- package/src/assets/cursors/prts-link-press-04.png +0 -0
- package/src/assets/cursors/prts-link-press-05.png +0 -0
- package/src/assets/cursors/prts-link-release-00.png +0 -0
- package/src/assets/cursors/prts-link-release-01.png +0 -0
- package/src/assets/cursors/prts-link-release-02.png +0 -0
- package/src/assets/cursors/prts-link-release-03.png +0 -0
- package/src/assets/cursors/prts-link-release-04.png +0 -0
- package/src/assets/cursors/prts-link-release-05.png +0 -0
- package/src/assets/cursors/prts-link-release-06.png +0 -0
- package/src/assets/cursors/prts-link-release-07.png +0 -0
- package/src/assets/cursors/prts-link-release-08.png +0 -0
- package/src/assets/cursors/prts-link-release-09.png +0 -0
- package/src/assets/cursors/prts-link-release-10.png +0 -0
- package/src/assets/cursors/prts-link-release-11.png +0 -0
- package/src/assets/cursors/prts-link.png +0 -0
- package/src/assets/cursors/prts-resize-ew.png +0 -0
- package/src/assets/cursors/prts-resize-nesw.png +0 -0
- package/src/assets/cursors/prts-resize-ns.png +0 -0
- package/src/assets/cursors/prts-resize-nwse.png +0 -0
- package/src/client/conversation-control-adapter.js +3 -1
- package/src/client/cursor-adapter.js +143 -34
- package/src/client/theme-settings-workbench.js +84 -7
- package/src/styles/cursor.css +5 -1
- package/src/styles/prts.css +26 -25
- package/src/styles/theme-settings-workbench.css +230 -0
package/package.json
CHANGED
|
@@ -424,5 +424,87 @@
|
|
|
424
424
|
"file": "prts-text-click-84.png",
|
|
425
425
|
"duration": 60000.0
|
|
426
426
|
}
|
|
427
|
-
]
|
|
427
|
+
],
|
|
428
|
+
"link": {
|
|
429
|
+
"hotspot": [
|
|
430
|
+
7,
|
|
431
|
+
7
|
|
432
|
+
],
|
|
433
|
+
"press": [
|
|
434
|
+
{
|
|
435
|
+
"file": "prts-link-press-00.png",
|
|
436
|
+
"duration": 16.666666666666668
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"file": "prts-link-press-01.png",
|
|
440
|
+
"duration": 16.666666666666668
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"file": "prts-link-press-02.png",
|
|
444
|
+
"duration": 16.666666666666668
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
"file": "prts-link-press-03.png",
|
|
448
|
+
"duration": 16.666666666666668
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"file": "prts-link-press-04.png",
|
|
452
|
+
"duration": 16.666666666666668
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"file": "prts-link-press-05.png",
|
|
456
|
+
"duration": 60000.0
|
|
457
|
+
}
|
|
458
|
+
],
|
|
459
|
+
"release": [
|
|
460
|
+
{
|
|
461
|
+
"file": "prts-link-release-00.png",
|
|
462
|
+
"duration": 16.666666666666668
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"file": "prts-link-release-01.png",
|
|
466
|
+
"duration": 16.666666666666668
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"file": "prts-link-release-02.png",
|
|
470
|
+
"duration": 16.666666666666668
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
"file": "prts-link-release-03.png",
|
|
474
|
+
"duration": 16.666666666666668
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
"file": "prts-link-release-04.png",
|
|
478
|
+
"duration": 16.666666666666668
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"file": "prts-link-release-05.png",
|
|
482
|
+
"duration": 16.666666666666668
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"file": "prts-link-release-06.png",
|
|
486
|
+
"duration": 16.666666666666668
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"file": "prts-link-release-07.png",
|
|
490
|
+
"duration": 16.666666666666668
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
"file": "prts-link-release-08.png",
|
|
494
|
+
"duration": 16.666666666666668
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
"file": "prts-link-release-09.png",
|
|
498
|
+
"duration": 16.666666666666668
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"file": "prts-link-release-10.png",
|
|
502
|
+
"duration": 16.666666666666668
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"file": "prts-link-release-11.png",
|
|
506
|
+
"duration": 60000.0
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
}
|
|
428
510
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -12,6 +12,8 @@ function closestWithClassSuffix(node, suffix) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function classify(button, operation) {
|
|
15
|
+
// The host's process disclosure owns its transparent, full-width hit area.
|
|
16
|
+
if (button.hasAttribute('data-turn-process')) return null
|
|
15
17
|
if (button.tagName !== 'BUTTON' || !operation.contains(button)) return null
|
|
16
18
|
if (hasClassSuffix(button, '_sessionLogButton')) return 'header'
|
|
17
19
|
if (button.closest('[data-produced-files-row]') || hasClassSuffix(button, '_showFolder')) return 'utility'
|
|
@@ -96,7 +98,7 @@ export function createConversationControlAdapter({ document, window }) {
|
|
|
96
98
|
subtree: true,
|
|
97
99
|
childList: true,
|
|
98
100
|
attributes: true,
|
|
99
|
-
attributeFilter: ['class', 'data-approval-key'],
|
|
101
|
+
attributeFilter: ['class', 'data-approval-key', 'data-turn-process'],
|
|
100
102
|
})
|
|
101
103
|
},
|
|
102
104
|
dispose() {
|
|
@@ -4,12 +4,121 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
4
4
|
let listening = false
|
|
5
5
|
let pointerId
|
|
6
6
|
let timer
|
|
7
|
-
let mode
|
|
8
|
-
let pressX
|
|
9
|
-
let pressY
|
|
10
7
|
let handPhase
|
|
8
|
+
let gestureAssets = assets
|
|
9
|
+
let textTarget
|
|
10
|
+
let lastPoint
|
|
11
|
+
const contentSelector = '[data-markdown], [data-slot*="markdown" i], [class*="markdown" i], [data-chat-flow-kind="user"] [class$="_bubble"]'
|
|
12
|
+
const controlSelector = 'a[href], button, summary, input, textarea, select, [contenteditable], [role="button"], [role="toolbar"], [role="tab"]'
|
|
13
|
+
function clearTextHit() {
|
|
14
|
+
textTarget?.removeAttribute('data-prts-text-hit')
|
|
15
|
+
textTarget = undefined
|
|
16
|
+
}
|
|
17
|
+
function updateTextHit(event) {
|
|
18
|
+
const nextTarget = findTextHit(event)
|
|
19
|
+
if (nextTarget === textTarget) return
|
|
20
|
+
clearTextHit()
|
|
21
|
+
textTarget = nextTarget
|
|
22
|
+
textTarget?.setAttribute('data-prts-text-hit', '')
|
|
23
|
+
}
|
|
24
|
+
function findTextHit(event) {
|
|
25
|
+
if (event.pointerType !== 'mouse') return
|
|
26
|
+
lastPoint = { clientX: event.clientX, clientY: event.clientY, pointerType: 'mouse' }
|
|
27
|
+
const target = event.target?.nodeType === 1 ? event.target : event.target?.parentElement
|
|
28
|
+
const content = target?.closest?.(contentSelector)
|
|
29
|
+
if (!content?.closest('[data-chat-flow-kind], [data-message-role]') || target.closest(controlSelector)) return
|
|
30
|
+
const style = window.getComputedStyle(target)
|
|
31
|
+
if (style.userSelect === 'none' || !style.cursor.includes('data:image/png')) return
|
|
32
|
+
// Caret APIs return the nearest insertion point even over empty space.
|
|
33
|
+
// Check its adjacent character rectangles before treating it as a text hit.
|
|
34
|
+
const caret = document.caretPositionFromPoint?.(event.clientX, event.clientY)
|
|
35
|
+
const fallback = caret ? undefined : document.caretRangeFromPoint?.(event.clientX, event.clientY)
|
|
36
|
+
const node = caret?.offsetNode ?? fallback?.startContainer
|
|
37
|
+
const offset = caret?.offset ?? fallback?.startOffset
|
|
38
|
+
if (node?.nodeType !== 3 || !content.contains(node) || node.parentElement?.closest(controlSelector)) return
|
|
39
|
+
if (window.getComputedStyle(node.parentElement).userSelect === 'none') return
|
|
40
|
+
const range = document.createRange()
|
|
41
|
+
for (const index of [offset - 1, offset]) {
|
|
42
|
+
if (index < 0 || index >= node.length) continue
|
|
43
|
+
range.setStart(node, index)
|
|
44
|
+
range.setEnd(node, index + 1)
|
|
45
|
+
for (const rect of range.getClientRects()) {
|
|
46
|
+
if (rect.width > 0 && event.clientX >= rect.left && event.clientX < rect.right && event.clientY >= rect.top && event.clientY < rect.bottom) {
|
|
47
|
+
return target
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function refreshTextHit() {
|
|
53
|
+
if (!lastPoint) return
|
|
54
|
+
updateTextHit({ ...lastPoint, target: document.elementFromPoint(lastPoint.clientX, lastPoint.clientY) })
|
|
55
|
+
}
|
|
56
|
+
let resizeTarget
|
|
57
|
+
let resizeOriginal
|
|
58
|
+
let resizePriority
|
|
59
|
+
let resizeApplied
|
|
60
|
+
function clearResize() {
|
|
61
|
+
if (resizeTarget && resizeTarget.style.cursor === resizeApplied) {
|
|
62
|
+
if (resizeOriginal) resizeTarget.style.setProperty('cursor', resizeOriginal, resizePriority)
|
|
63
|
+
else resizeTarget.style.removeProperty('cursor')
|
|
64
|
+
}
|
|
65
|
+
resizeTarget = undefined
|
|
66
|
+
}
|
|
67
|
+
function updateResize(event) {
|
|
68
|
+
if (event.pointerType !== 'mouse' || !assets.resize) return
|
|
69
|
+
const target = event.target?.nodeType === 1 ? event.target : event.target?.parentElement
|
|
70
|
+
if (target === resizeTarget && target.style.cursor === resizeApplied) return
|
|
71
|
+
clearResize()
|
|
72
|
+
if (!target) return
|
|
73
|
+
const cursor = window.getComputedStyle(target).cursor
|
|
74
|
+
const direction = { 'n-resize': 'ns', 's-resize': 'ns', 'ns-resize': 'ns', 'row-resize': 'ns',
|
|
75
|
+
'e-resize': 'ew', 'w-resize': 'ew', 'ew-resize': 'ew', 'col-resize': 'ew',
|
|
76
|
+
'ne-resize': 'nesw', 'sw-resize': 'nesw', 'nesw-resize': 'nesw',
|
|
77
|
+
'nw-resize': 'nwse', 'se-resize': 'nwse', 'nwse-resize': 'nwse' }[cursor]
|
|
78
|
+
if (!direction) return
|
|
79
|
+
resizeTarget = target
|
|
80
|
+
resizeOriginal = target.style.getPropertyValue('cursor')
|
|
81
|
+
resizePriority = target.style.getPropertyPriority('cursor')
|
|
82
|
+
target.style.setProperty('cursor', `url("${assets.resize[direction]}") 16 16, ${cursor}`, resizePriority)
|
|
83
|
+
resizeApplied = target.style.cursor
|
|
84
|
+
}
|
|
11
85
|
const reducedMedia = window?.matchMedia?.('(prefers-reduced-motion: reduce)')
|
|
12
|
-
|
|
86
|
+
let handTarget
|
|
87
|
+
let handCursor
|
|
88
|
+
let originalCursor
|
|
89
|
+
let originalPriority
|
|
90
|
+
let appliedCursor
|
|
91
|
+
function restoreHandTarget() {
|
|
92
|
+
if (handTarget && handTarget.style.cursor === appliedCursor) {
|
|
93
|
+
if (originalCursor) handTarget.style.setProperty('cursor', originalCursor, originalPriority)
|
|
94
|
+
else handTarget.style.removeProperty('cursor')
|
|
95
|
+
}
|
|
96
|
+
handTarget?.removeAttribute('data-prts-hand-active')
|
|
97
|
+
handTarget = undefined
|
|
98
|
+
}
|
|
99
|
+
// Cursor is inherited. Animating a large container still restyles its subtree.
|
|
100
|
+
// Keep those background hits static, while controls retain native animation.
|
|
101
|
+
const maxAnimatedDescendants = 128
|
|
102
|
+
function canAnimate(target) {
|
|
103
|
+
return target && target.getElementsByTagName('*').length <= maxAnimatedDescendants
|
|
104
|
+
}
|
|
105
|
+
function followHand(target) {
|
|
106
|
+
if (target === handTarget && target !== textTarget) return
|
|
107
|
+
restoreHandTarget()
|
|
108
|
+
if (!canAnimate(target) || !handCursor || target === textTarget) return
|
|
109
|
+
const cursor = window.getComputedStyle(target).cursor
|
|
110
|
+
if (!cursor.includes('data:image/png') || /(?:,\s*text|resize)$/.test(cursor)) return
|
|
111
|
+
handTarget = target
|
|
112
|
+
originalCursor = target.style.getPropertyValue('cursor')
|
|
113
|
+
originalPriority = target.style.getPropertyPriority('cursor')
|
|
114
|
+
target.setAttribute('data-prts-hand-active', '')
|
|
115
|
+
applyHandFrame()
|
|
116
|
+
}
|
|
117
|
+
function applyHandFrame() {
|
|
118
|
+
if (!handTarget) return
|
|
119
|
+
handTarget.style.setProperty('cursor', handCursor)
|
|
120
|
+
appliedCursor = handTarget.style.cursor
|
|
121
|
+
}
|
|
13
122
|
function clearTimer() {
|
|
14
123
|
if (timer !== undefined) window.clearTimeout(timer)
|
|
15
124
|
timer = undefined
|
|
@@ -17,14 +126,15 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
17
126
|
function reset() {
|
|
18
127
|
clearTimer()
|
|
19
128
|
pointerId = undefined
|
|
20
|
-
|
|
129
|
+
restoreHandTarget()
|
|
130
|
+
handCursor = undefined
|
|
21
131
|
root?.style.removeProperty('--prts-text-frame')
|
|
22
|
-
mode = undefined
|
|
23
132
|
handPhase = undefined
|
|
24
133
|
}
|
|
25
134
|
function show(url) {
|
|
26
|
-
const [x, y] =
|
|
27
|
-
|
|
135
|
+
const [x, y] = gestureAssets.hotspot
|
|
136
|
+
handCursor = `url("${url}") ${x} ${y}, pointer`
|
|
137
|
+
applyHandFrame()
|
|
28
138
|
}
|
|
29
139
|
function reduced() {
|
|
30
140
|
return root.dataset.prtsMotion === 'reduced' || reducedMedia?.matches
|
|
@@ -44,59 +154,58 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
44
154
|
}
|
|
45
155
|
function down(event) {
|
|
46
156
|
if (event.pointerType !== 'mouse' || event.button !== 0 || event.isPrimary === false) return
|
|
157
|
+
updateResize(event)
|
|
158
|
+
updateTextHit(event)
|
|
47
159
|
const target = event.target?.nodeType === 1 ? event.target : event.target?.parentElement
|
|
48
|
-
const cursor = target ? window.getComputedStyle(target).cursor : ''
|
|
49
160
|
reset()
|
|
161
|
+
const cursor = target ? window.getComputedStyle(target).cursor : ''
|
|
50
162
|
if (!cursor.includes('data:image/png')) return
|
|
51
|
-
|
|
163
|
+
if (/(?:,\s*text|resize)$/.test(cursor) || !canAnimate(target)) return
|
|
164
|
+
gestureAssets = /,\s*pointer$/.test(cursor) && assets.link ? assets.link : assets
|
|
52
165
|
pointerId = event.pointerId
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
if (reduced() || !assets.textClick) { reset(); return }
|
|
57
|
-
play(assets.textClick, reset)
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
if (reduced()) { clearTimer(); show(assets.hold); return }
|
|
166
|
+
handCursor = cursor
|
|
167
|
+
followHand(target)
|
|
168
|
+
if (reduced()) { clearTimer(); show(gestureAssets.hold); return }
|
|
61
169
|
handPhase = 'press'
|
|
62
|
-
play(
|
|
170
|
+
play(gestureAssets.press, () => {
|
|
63
171
|
handPhase = 'hold'
|
|
64
|
-
show(
|
|
172
|
+
show(gestureAssets.hold)
|
|
65
173
|
// A quick tap must still reach a visible pressed pose before recovering.
|
|
66
174
|
if (pointerId === undefined) timer = window.setTimeout(releaseHand, 48)
|
|
67
175
|
})
|
|
68
176
|
}
|
|
69
177
|
function releaseHand() {
|
|
70
178
|
handPhase = 'release'
|
|
71
|
-
play(
|
|
179
|
+
play(gestureAssets.release, reset)
|
|
72
180
|
}
|
|
73
181
|
function up(event) {
|
|
74
182
|
if (pointerId === undefined || event.pointerId !== pointerId || event.button !== 0) return
|
|
75
183
|
pointerId = undefined
|
|
76
|
-
if (mode === 'text') return
|
|
77
184
|
if (reduced()) { reset(); return }
|
|
78
185
|
if (handPhase === 'press') return
|
|
79
186
|
releaseHand()
|
|
80
187
|
}
|
|
81
188
|
function move(event) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const selecting = pointerId === event.pointerId && (event.buttons & 1) && Math.hypot(event.clientX - pressX, event.clientY - pressY) > 4
|
|
86
|
-
if (!/,\s*text$/.test(cursor) || selecting) reset()
|
|
87
|
-
return
|
|
88
|
-
}
|
|
189
|
+
updateResize(event)
|
|
190
|
+
updateTextHit(event)
|
|
191
|
+
if (handCursor) followHand(event.target?.nodeType === 1 ? event.target : event.target?.parentElement)
|
|
89
192
|
if (pointerId !== undefined && event.pointerId === pointerId && !(event.buttons & 1)) reset()
|
|
90
193
|
}
|
|
91
|
-
function
|
|
194
|
+
function resetInteraction() {
|
|
195
|
+
reset()
|
|
196
|
+
clearResize()
|
|
197
|
+
clearTextHit()
|
|
198
|
+
lastPoint = undefined
|
|
199
|
+
}
|
|
200
|
+
function leave(event) { if (!event.relatedTarget) resetInteraction() }
|
|
92
201
|
function blur(event) {
|
|
93
202
|
// Captured element blur also reaches window when focus changes inside the app.
|
|
94
|
-
if (event.target === window)
|
|
203
|
+
if (event.target === window) resetInteraction()
|
|
95
204
|
}
|
|
96
|
-
function visibility() { if (document.hidden)
|
|
97
|
-
const events = [['pointerdown', down], ['pointerup', up], ['pointermove', move], ['pointercancel',
|
|
205
|
+
function visibility() { if (document.hidden) resetInteraction() }
|
|
206
|
+
const events = [['pointerdown', down], ['pointerup', up], ['pointermove', move], ['pointercancel', resetInteraction], ['blur', blur], ['dragstart', resetInteraction], ['pointerout', leave], ['scroll', refreshTextHit], ['resize', refreshTextHit]]
|
|
98
207
|
function dispose() {
|
|
99
|
-
|
|
208
|
+
resetInteraction()
|
|
100
209
|
if (!listening) return
|
|
101
210
|
for (const [name, handler] of events) window.removeEventListener(name, handler, true)
|
|
102
211
|
document.removeEventListener('visibilitychange', visibility)
|
|
@@ -15,7 +15,7 @@ const PRESET_OPTIONS = [
|
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
const COMMON_CONTROLS = [
|
|
18
|
-
['cursor', '
|
|
18
|
+
['cursor', 'PRTS 光标', [['system', '关闭'], ['prts', '开启']]],
|
|
19
19
|
['railDefaultHidden', '默认隐藏导航栏', [['false', '关闭'], ['true', '开启']]],
|
|
20
20
|
['texture', '环境底纹', [['off', '关闭'], ['restrained', '克制'], ['full', '完整']]],
|
|
21
21
|
['glass', '玻璃材质', [['off', '关闭'], ['soft', '柔和'], ['standard', '标准'], ['clear', '清晰']]],
|
|
@@ -53,6 +53,10 @@ function settingButtons(key, label, options) {
|
|
|
53
53
|
: key === 'sessionFlowPalette'
|
|
54
54
|
? '<small data-prts-setting-note>同时适配日间与夜间模式</small>'
|
|
55
55
|
: ''
|
|
56
|
+
if (['cursor', 'railDefaultHidden', 'sessionFlow', 'sessionGlow'].includes(key)) return `<div data-prts-setting-row="${key}">
|
|
57
|
+
<span><strong>${label}</strong>${note}</span>
|
|
58
|
+
<button type="button" role="switch" aria-label="${label}" aria-checked="false" data-prts-setting-switch="${key}" ${key === 'cursor' ? 'data-prts-cursor-switch' : ''}><span aria-hidden="true"></span></button>
|
|
59
|
+
</div>`
|
|
56
60
|
const sample = value => key === 'texture' || key === 'glass' || key === 'sessionFlowPalette'
|
|
57
61
|
? `<i data-prts-option-sample="${key}" data-prts-option-sample-value="${value}" aria-hidden="true"></i>`
|
|
58
62
|
: ''
|
|
@@ -114,26 +118,50 @@ export function themeSettingsMarkup() {
|
|
|
114
118
|
<button type="button" data-prts-theme-settings-close aria-label="关闭主题设置">×</button>
|
|
115
119
|
</header>
|
|
116
120
|
|
|
121
|
+
<div data-prts-settings-body>
|
|
122
|
+
<nav data-prts-settings-nav aria-label="设置分区">
|
|
123
|
+
<button type="button" data-prts-settings-jump="appearance">外观</button>
|
|
124
|
+
<button type="button" data-prts-settings-jump="interaction">交互</button>
|
|
125
|
+
<button type="button" data-prts-settings-jump="flow">会话流线</button>
|
|
126
|
+
<button type="button" data-prts-settings-jump="particles">粒子效果</button>
|
|
127
|
+
</nav>
|
|
117
128
|
<div data-prts-theme-settings-scroll>
|
|
129
|
+
<section data-prts-settings-section="appearance" tabindex="-1">
|
|
130
|
+
<header data-prts-section-heading><strong>外观</strong><small>预设、界面材质与会话样式</small></header>
|
|
118
131
|
<section data-prts-settings-presets>
|
|
119
|
-
<header><span><
|
|
132
|
+
<header><span><strong>视觉预设</strong></span></header>
|
|
120
133
|
<div>${presetMarkup()}</div>
|
|
121
134
|
</section>
|
|
122
135
|
|
|
123
136
|
<section data-prts-settings-common>
|
|
124
|
-
<header><span><
|
|
125
|
-
<div data-prts-settings-common-grid
|
|
137
|
+
<header><span><strong>界面外观</strong></span></header>
|
|
138
|
+
<div data-prts-settings-common-grid>
|
|
139
|
+
${COMMON_CONTROLS.filter(([key]) => ['texture', 'glass'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
140
|
+
${settingButtons('conversationStyle', '会话主题', [['native', '原有'], ['deck-chat', '通讯链路']])}
|
|
141
|
+
</div>
|
|
142
|
+
</section>
|
|
143
|
+
</section>
|
|
144
|
+
<section data-prts-settings-common data-prts-settings-section="interaction" tabindex="-1">
|
|
145
|
+
<header data-prts-section-heading><strong>交互</strong><small>光标、导航栏与动态反馈</small></header>
|
|
146
|
+
<div data-prts-settings-common-grid>
|
|
147
|
+
${COMMON_CONTROLS.filter(([key]) => ['cursor', 'railDefaultHidden', 'motion'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
148
|
+
</div>
|
|
149
|
+
</section>
|
|
150
|
+
<section data-prts-settings-common data-prts-settings-section="flow" tabindex="-1">
|
|
151
|
+
<header data-prts-section-heading><strong>会话流线</strong><small>当前会话的流线与辉光</small></header>
|
|
152
|
+
<div data-prts-settings-common-grid>
|
|
153
|
+
${COMMON_CONTROLS.filter(([key]) => ['sessionFlow', 'sessionGlow', 'sessionFlowPalette'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
126
154
|
${sessionFlowColorMarkup()}
|
|
127
155
|
<label data-prts-setting-row="sessionFlowSpeed" data-prts-setting-range-row data-prts-session-flow-speed>
|
|
128
156
|
<span><strong>流线速度</strong><small data-prts-setting-note>控制当前会话流线的形变速度</small></span>
|
|
129
157
|
<div><input type="range" min="0.25" max="2" step="0.25" value="1" data-prts-setting-range data-prts-setting-key="sessionFlowSpeed" aria-label="当前会话流线速度"><output data-prts-session-flow-speed-output>1× · 标准</output></div>
|
|
130
158
|
</label>
|
|
131
|
-
|
|
159
|
+
|
|
132
160
|
</div>
|
|
133
161
|
</section>
|
|
134
162
|
|
|
135
|
-
<details data-prts-settings-advanced>
|
|
136
|
-
<summary><span><
|
|
163
|
+
<details data-prts-settings-advanced data-prts-settings-section="particles" tabindex="-1">
|
|
164
|
+
<summary data-prts-section-heading><span><strong>粒子效果</strong><small>徽记精度与跟随移动速度</small></span><output data-prts-advanced-summary>标准 / 1×</output></summary>
|
|
137
165
|
<div data-prts-settings-advanced-content>
|
|
138
166
|
<section data-prts-particle-detail>
|
|
139
167
|
<header><strong>粒子精度</strong><span>同时调整会话与新会话徽记</span></header>
|
|
@@ -163,6 +191,7 @@ export function themeSettingsMarkup() {
|
|
|
163
191
|
</div>
|
|
164
192
|
</footer>
|
|
165
193
|
</div>
|
|
194
|
+
</div>
|
|
166
195
|
</div>
|
|
167
196
|
</dialog>`
|
|
168
197
|
}
|
|
@@ -429,6 +458,18 @@ export function createThemeSettingsOverlay({
|
|
|
429
458
|
|
|
430
459
|
function renderState() {
|
|
431
460
|
if (!panel || !currentPreferences) return
|
|
461
|
+
for (const toggle of panel.querySelectorAll('[data-prts-setting-switch]')) {
|
|
462
|
+
const key = toggle.dataset.prtsSettingSwitch
|
|
463
|
+
const enabled = key === 'cursor' ? currentPreferences.cursor === 'prts'
|
|
464
|
+
: key === 'railDefaultHidden' ? currentPreferences[key] === true : currentPreferences[key] !== false
|
|
465
|
+
toggle.setAttribute('aria-checked', String(enabled))
|
|
466
|
+
toggle.classList.toggle('is-active', enabled)
|
|
467
|
+
}
|
|
468
|
+
for (const key of ['sessionGlow', 'sessionFlowPalette', 'sessionFlowColors', 'sessionFlowSpeed']) {
|
|
469
|
+
const row = panel.querySelector(`[data-prts-setting-row="${key}"]`)
|
|
470
|
+
if (row) row.hidden = currentPreferences.sessionFlow === false
|
|
471
|
+
|| (key === 'sessionFlowColors' && activeSessionFlowPalette() !== 'custom')
|
|
472
|
+
}
|
|
432
473
|
const particleDetail = resolveParticleDetail(currentPreferences)
|
|
433
474
|
for (const button of panel.querySelectorAll('button[data-prts-setting-key]')) {
|
|
434
475
|
const key = button.dataset.prtsSettingKey
|
|
@@ -478,11 +519,44 @@ export function createThemeSettingsOverlay({
|
|
|
478
519
|
}, 300)
|
|
479
520
|
}
|
|
480
521
|
|
|
522
|
+
function syncSectionNavigation() {
|
|
523
|
+
const scroller = panel?.querySelector('[data-prts-theme-settings-scroll]')
|
|
524
|
+
if (!scroller || !scroller.clientHeight) return
|
|
525
|
+
const sections = [...panel.querySelectorAll('[data-prts-settings-section]')]
|
|
526
|
+
const top = scroller.getBoundingClientRect().top + 32
|
|
527
|
+
let current = sections[0]
|
|
528
|
+
for (const section of sections) {
|
|
529
|
+
if (section.getBoundingClientRect().top <= top) current = section
|
|
530
|
+
}
|
|
531
|
+
if (scroller.scrollTop > 0 && scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2) current = sections.at(-1)
|
|
532
|
+
for (const button of panel.querySelectorAll('[data-prts-settings-jump]')) {
|
|
533
|
+
if (button.dataset.prtsSettingsJump === current?.dataset.prtsSettingsSection) button.setAttribute('aria-current', 'location')
|
|
534
|
+
else button.removeAttribute('aria-current')
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
481
538
|
function onPanelClick(event) {
|
|
482
539
|
if (event.target === panel) { setOpen(false); return }
|
|
483
540
|
const target = event.target?.closest?.('button')
|
|
484
541
|
if (!target) return
|
|
542
|
+
if (target.hasAttribute('data-prts-settings-jump')) {
|
|
543
|
+
const section = panel.querySelector(`[data-prts-settings-section="${target.dataset.prtsSettingsJump}"]`)
|
|
544
|
+
const scroller = panel.querySelector('[data-prts-theme-settings-scroll]')
|
|
545
|
+
if (section && scroller) {
|
|
546
|
+
if (section.tagName === 'DETAILS') section.open = true
|
|
547
|
+
scroller.scrollTo?.({ top: target.dataset.prtsSettingsJump === 'appearance' ? 0 : scroller.scrollTop + section.getBoundingClientRect().top - scroller.getBoundingClientRect().top - 16, behavior: 'instant' })
|
|
548
|
+
section.focus?.({ preventScroll: true })
|
|
549
|
+
syncSectionNavigation()
|
|
550
|
+
}
|
|
551
|
+
return
|
|
552
|
+
}
|
|
485
553
|
if (target.hasAttribute('data-prts-theme-settings-close')) { setOpen(false); return }
|
|
554
|
+
if (target.hasAttribute('data-prts-setting-switch')) {
|
|
555
|
+
const key = target.dataset.prtsSettingSwitch
|
|
556
|
+
const enabled = target.getAttribute('aria-checked') === 'true'
|
|
557
|
+
onPreferenceChange(key, key === 'cursor' ? (enabled ? 'system' : 'prts') : !enabled)
|
|
558
|
+
return
|
|
559
|
+
}
|
|
486
560
|
if (target.hasAttribute('data-prts-setting-key')) {
|
|
487
561
|
const raw = target.dataset.prtsSettingValue
|
|
488
562
|
if (target.dataset.prtsSettingKey === 'sessionFlowPalette') {
|
|
@@ -568,6 +642,8 @@ export function createThemeSettingsOverlay({
|
|
|
568
642
|
|
|
569
643
|
trigger?.addEventListener('click', toggle)
|
|
570
644
|
panel?.addEventListener('click', onPanelClick)
|
|
645
|
+
panel?.querySelector('[data-prts-theme-settings-scroll]')?.addEventListener('scroll', syncSectionNavigation, { passive: true })
|
|
646
|
+
panel?.querySelector('[data-prts-settings-jump]')?.setAttribute('aria-current', 'location')
|
|
571
647
|
panel?.addEventListener('cancel', onCancel)
|
|
572
648
|
panel?.addEventListener('input', onPanelInput)
|
|
573
649
|
panel?.addEventListener('change', commitFromControl)
|
|
@@ -602,6 +678,7 @@ export function createThemeSettingsOverlay({
|
|
|
602
678
|
trigger?.setAttribute('aria-expanded', 'false')
|
|
603
679
|
trigger?.removeEventListener('click', toggle)
|
|
604
680
|
panel?.removeEventListener('click', onPanelClick)
|
|
681
|
+
panel?.querySelector('[data-prts-theme-settings-scroll]')?.removeEventListener('scroll', syncSectionNavigation)
|
|
605
682
|
panel?.removeEventListener('cancel', onCancel)
|
|
606
683
|
panel?.removeEventListener('input', onPanelInput)
|
|
607
684
|
panel?.removeEventListener('change', commitFromControl)
|
package/src/styles/cursor.css
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
html[data-dsh-prts][data-prts-cursor="prts"] {
|
|
4
4
|
--prts-hand-cursor: var(--prts-hand-frame, url("__PRTS_HAND__") 7 6);
|
|
5
5
|
--prts-text-cursor: var(--prts-text-frame, url("__PRTS_TEXT__") 24 24);
|
|
6
|
+
--prts-link-cursor: url("__PRTS_LINK__") 7 7;
|
|
6
7
|
cursor: var(--prts-hand-cursor), default;
|
|
7
8
|
}
|
|
8
9
|
html[data-dsh-prts][data-prts-cursor="prts"] :is(button, a[href], summary, [role="button"], [role="tab"], [role="treeitem"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"], [data-prts-workspace-row], [data-prts-session-row], .dsh-partner-sidebar-row, select, input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"], input[type="color"], input[type="range"]) {
|
|
9
|
-
cursor: var(--prts-
|
|
10
|
+
cursor: var(--prts-link-cursor), pointer;
|
|
11
|
+
}
|
|
12
|
+
html[data-dsh-prts][data-prts-cursor="prts"] [data-prts-text-hit] {
|
|
13
|
+
cursor: var(--prts-text-cursor), text;
|
|
10
14
|
}
|
|
11
15
|
html[data-dsh-prts][data-prts-cursor="prts"] :is(textarea, input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], input[type="number"], input[type="tel"], [contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"]) {
|
|
12
16
|
cursor: var(--prts-text-cursor), text;
|