@ash-qw/dsh-theme-prts 1.0.3 → 1.0.5
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 +3 -1
- package/README.md +4 -2
- package/THIRD_PARTY_NOTICES.md +9 -12
- package/THIRD_PARTY_NOTICES.zh-CN.md +45 -0
- package/lib/client.js +291 -11
- package/package.json +3 -2
- package/src/assets/cursors/animation.json +510 -0
- package/src/assets/cursors/prts-hand-hold.png +0 -0
- package/src/assets/cursors/prts-hand-press-00.png +0 -0
- package/src/assets/cursors/prts-hand-press-01.png +0 -0
- package/src/assets/cursors/prts-hand-press-02.png +0 -0
- package/src/assets/cursors/prts-hand-press-03.png +0 -0
- package/src/assets/cursors/prts-hand-press-04.png +0 -0
- package/src/assets/cursors/prts-hand-press-05.png +0 -0
- package/src/assets/cursors/prts-hand-release-00.png +0 -0
- package/src/assets/cursors/prts-hand-release-01.png +0 -0
- package/src/assets/cursors/prts-hand-release-02.png +0 -0
- package/src/assets/cursors/prts-hand-release-03.png +0 -0
- package/src/assets/cursors/prts-hand-release-04.png +0 -0
- package/src/assets/cursors/prts-hand-release-05.png +0 -0
- package/src/assets/cursors/prts-hand-release-06.png +0 -0
- package/src/assets/cursors/prts-hand-release-07.png +0 -0
- package/src/assets/cursors/prts-hand-release-08.png +0 -0
- package/src/assets/cursors/prts-hand-release-09.png +0 -0
- package/src/assets/cursors/prts-hand-release-10.png +0 -0
- package/src/assets/cursors/prts-hand-release-11.png +0 -0
- package/src/assets/cursors/prts-hand.png +0 -0
- 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/assets/cursors/prts-text-click-00.png +0 -0
- package/src/assets/cursors/prts-text-click-01.png +0 -0
- package/src/assets/cursors/prts-text-click-02.png +0 -0
- package/src/assets/cursors/prts-text-click-03.png +0 -0
- package/src/assets/cursors/prts-text-click-04.png +0 -0
- package/src/assets/cursors/prts-text-click-05.png +0 -0
- package/src/assets/cursors/prts-text-click-06.png +0 -0
- package/src/assets/cursors/prts-text-click-07.png +0 -0
- package/src/assets/cursors/prts-text-click-08.png +0 -0
- package/src/assets/cursors/prts-text-click-09.png +0 -0
- package/src/assets/cursors/prts-text-click-10.png +0 -0
- package/src/assets/cursors/prts-text-click-11.png +0 -0
- package/src/assets/cursors/prts-text-click-12.png +0 -0
- package/src/assets/cursors/prts-text-click-13.png +0 -0
- package/src/assets/cursors/prts-text-click-14.png +0 -0
- package/src/assets/cursors/prts-text-click-15.png +0 -0
- package/src/assets/cursors/prts-text-click-16.png +0 -0
- package/src/assets/cursors/prts-text-click-17.png +0 -0
- package/src/assets/cursors/prts-text-click-18.png +0 -0
- package/src/assets/cursors/prts-text-click-19.png +0 -0
- package/src/assets/cursors/prts-text-click-20.png +0 -0
- package/src/assets/cursors/prts-text-click-21.png +0 -0
- package/src/assets/cursors/prts-text-click-22.png +0 -0
- package/src/assets/cursors/prts-text-click-23.png +0 -0
- package/src/assets/cursors/prts-text-click-24.png +0 -0
- package/src/assets/cursors/prts-text-click-25.png +0 -0
- package/src/assets/cursors/prts-text-click-26.png +0 -0
- package/src/assets/cursors/prts-text-click-27.png +0 -0
- package/src/assets/cursors/prts-text-click-28.png +0 -0
- package/src/assets/cursors/prts-text-click-29.png +0 -0
- package/src/assets/cursors/prts-text-click-30.png +0 -0
- package/src/assets/cursors/prts-text-click-31.png +0 -0
- package/src/assets/cursors/prts-text-click-32.png +0 -0
- package/src/assets/cursors/prts-text-click-33.png +0 -0
- package/src/assets/cursors/prts-text-click-34.png +0 -0
- package/src/assets/cursors/prts-text-click-35.png +0 -0
- package/src/assets/cursors/prts-text-click-36.png +0 -0
- package/src/assets/cursors/prts-text-click-37.png +0 -0
- package/src/assets/cursors/prts-text-click-38.png +0 -0
- package/src/assets/cursors/prts-text-click-39.png +0 -0
- package/src/assets/cursors/prts-text-click-40.png +0 -0
- package/src/assets/cursors/prts-text-click-41.png +0 -0
- package/src/assets/cursors/prts-text-click-42.png +0 -0
- package/src/assets/cursors/prts-text-click-43.png +0 -0
- package/src/assets/cursors/prts-text-click-44.png +0 -0
- package/src/assets/cursors/prts-text-click-45.png +0 -0
- package/src/assets/cursors/prts-text-click-46.png +0 -0
- package/src/assets/cursors/prts-text-click-47.png +0 -0
- package/src/assets/cursors/prts-text-click-48.png +0 -0
- package/src/assets/cursors/prts-text-click-49.png +0 -0
- package/src/assets/cursors/prts-text-click-50.png +0 -0
- package/src/assets/cursors/prts-text-click-51.png +0 -0
- package/src/assets/cursors/prts-text-click-52.png +0 -0
- package/src/assets/cursors/prts-text-click-53.png +0 -0
- package/src/assets/cursors/prts-text-click-54.png +0 -0
- package/src/assets/cursors/prts-text-click-55.png +0 -0
- package/src/assets/cursors/prts-text-click-56.png +0 -0
- package/src/assets/cursors/prts-text-click-57.png +0 -0
- package/src/assets/cursors/prts-text-click-58.png +0 -0
- package/src/assets/cursors/prts-text-click-59.png +0 -0
- package/src/assets/cursors/prts-text-click-60.png +0 -0
- package/src/assets/cursors/prts-text-click-61.png +0 -0
- package/src/assets/cursors/prts-text-click-62.png +0 -0
- package/src/assets/cursors/prts-text-click-63.png +0 -0
- package/src/assets/cursors/prts-text-click-64.png +0 -0
- package/src/assets/cursors/prts-text-click-65.png +0 -0
- package/src/assets/cursors/prts-text-click-66.png +0 -0
- package/src/assets/cursors/prts-text-click-67.png +0 -0
- package/src/assets/cursors/prts-text-click-68.png +0 -0
- package/src/assets/cursors/prts-text-click-69.png +0 -0
- package/src/assets/cursors/prts-text-click-70.png +0 -0
- package/src/assets/cursors/prts-text-click-71.png +0 -0
- package/src/assets/cursors/prts-text-click-72.png +0 -0
- package/src/assets/cursors/prts-text-click-73.png +0 -0
- package/src/assets/cursors/prts-text-click-74.png +0 -0
- package/src/assets/cursors/prts-text-click-75.png +0 -0
- package/src/assets/cursors/prts-text-click-76.png +0 -0
- package/src/assets/cursors/prts-text-click-77.png +0 -0
- package/src/assets/cursors/prts-text-click-78.png +0 -0
- package/src/assets/cursors/prts-text-click-79.png +0 -0
- package/src/assets/cursors/prts-text-click-80.png +0 -0
- package/src/assets/cursors/prts-text-click-81.png +0 -0
- package/src/assets/cursors/prts-text-click-82.png +0 -0
- package/src/assets/cursors/prts-text-click-83.png +0 -0
- package/src/assets/cursors/prts-text-click-84.png +0 -0
- package/src/assets/cursors/prts-text.png +0 -0
- package/src/client/cursor-adapter.js +224 -0
- package/src/client/index.js +1 -0
- package/src/client/preferences.js +3 -1
- package/src/client/theme-controller-v2.js +7 -1
- package/src/client/theme-settings-workbench.js +62 -7
- package/src/styles/cursor.css +21 -0
- package/src/styles/prts.css +7 -7
- package/src/styles/theme-settings-workbench.css +162 -0
|
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
|
|
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
|
|
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
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Keep the native cursor and change its image only during a primary mouse gesture.
|
|
2
|
+
export function createCursorAdapter({ document, window, assets }) {
|
|
3
|
+
const root = document?.documentElement
|
|
4
|
+
let listening = false
|
|
5
|
+
let pointerId
|
|
6
|
+
let timer
|
|
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
|
+
}
|
|
85
|
+
const reducedMedia = window?.matchMedia?.('(prefers-reduced-motion: reduce)')
|
|
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
|
+
}
|
|
122
|
+
function clearTimer() {
|
|
123
|
+
if (timer !== undefined) window.clearTimeout(timer)
|
|
124
|
+
timer = undefined
|
|
125
|
+
}
|
|
126
|
+
function reset() {
|
|
127
|
+
clearTimer()
|
|
128
|
+
pointerId = undefined
|
|
129
|
+
restoreHandTarget()
|
|
130
|
+
handCursor = undefined
|
|
131
|
+
root?.style.removeProperty('--prts-text-frame')
|
|
132
|
+
handPhase = undefined
|
|
133
|
+
}
|
|
134
|
+
function show(url) {
|
|
135
|
+
const [x, y] = gestureAssets.hotspot
|
|
136
|
+
handCursor = `url("${url}") ${x} ${y}, pointer`
|
|
137
|
+
applyHandFrame()
|
|
138
|
+
}
|
|
139
|
+
function reduced() {
|
|
140
|
+
return root.dataset.prtsMotion === 'reduced' || reducedMedia?.matches
|
|
141
|
+
}
|
|
142
|
+
function play(frames, done) {
|
|
143
|
+
clearTimer()
|
|
144
|
+
let index = 0
|
|
145
|
+
function next() {
|
|
146
|
+
timer = undefined
|
|
147
|
+
// The source ANI ends with a 60-second hold frame. Use the static state instead.
|
|
148
|
+
if (index >= frames.length - 1) { done(); return }
|
|
149
|
+
const frame = frames[index++]
|
|
150
|
+
show(frame.url)
|
|
151
|
+
timer = window.setTimeout(next, frame.duration)
|
|
152
|
+
}
|
|
153
|
+
next()
|
|
154
|
+
}
|
|
155
|
+
function down(event) {
|
|
156
|
+
if (event.pointerType !== 'mouse' || event.button !== 0 || event.isPrimary === false) return
|
|
157
|
+
updateResize(event)
|
|
158
|
+
updateTextHit(event)
|
|
159
|
+
const target = event.target?.nodeType === 1 ? event.target : event.target?.parentElement
|
|
160
|
+
reset()
|
|
161
|
+
const cursor = target ? window.getComputedStyle(target).cursor : ''
|
|
162
|
+
if (!cursor.includes('data:image/png')) return
|
|
163
|
+
if (/(?:,\s*text|resize)$/.test(cursor) || !canAnimate(target)) return
|
|
164
|
+
gestureAssets = /,\s*pointer$/.test(cursor) && assets.link ? assets.link : assets
|
|
165
|
+
pointerId = event.pointerId
|
|
166
|
+
handCursor = cursor
|
|
167
|
+
followHand(target)
|
|
168
|
+
if (reduced()) { clearTimer(); show(gestureAssets.hold); return }
|
|
169
|
+
handPhase = 'press'
|
|
170
|
+
play(gestureAssets.press, () => {
|
|
171
|
+
handPhase = 'hold'
|
|
172
|
+
show(gestureAssets.hold)
|
|
173
|
+
// A quick tap must still reach a visible pressed pose before recovering.
|
|
174
|
+
if (pointerId === undefined) timer = window.setTimeout(releaseHand, 48)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
function releaseHand() {
|
|
178
|
+
handPhase = 'release'
|
|
179
|
+
play(gestureAssets.release, reset)
|
|
180
|
+
}
|
|
181
|
+
function up(event) {
|
|
182
|
+
if (pointerId === undefined || event.pointerId !== pointerId || event.button !== 0) return
|
|
183
|
+
pointerId = undefined
|
|
184
|
+
if (reduced()) { reset(); return }
|
|
185
|
+
if (handPhase === 'press') return
|
|
186
|
+
releaseHand()
|
|
187
|
+
}
|
|
188
|
+
function move(event) {
|
|
189
|
+
updateResize(event)
|
|
190
|
+
updateTextHit(event)
|
|
191
|
+
if (handCursor) followHand(event.target?.nodeType === 1 ? event.target : event.target?.parentElement)
|
|
192
|
+
if (pointerId !== undefined && event.pointerId === pointerId && !(event.buttons & 1)) reset()
|
|
193
|
+
}
|
|
194
|
+
function resetInteraction() {
|
|
195
|
+
reset()
|
|
196
|
+
clearResize()
|
|
197
|
+
clearTextHit()
|
|
198
|
+
lastPoint = undefined
|
|
199
|
+
}
|
|
200
|
+
function leave(event) { if (!event.relatedTarget) resetInteraction() }
|
|
201
|
+
function blur(event) {
|
|
202
|
+
// Captured element blur also reaches window when focus changes inside the app.
|
|
203
|
+
if (event.target === window) resetInteraction()
|
|
204
|
+
}
|
|
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]]
|
|
207
|
+
function dispose() {
|
|
208
|
+
resetInteraction()
|
|
209
|
+
if (!listening) return
|
|
210
|
+
for (const [name, handler] of events) window.removeEventListener(name, handler, true)
|
|
211
|
+
document.removeEventListener('visibilitychange', visibility)
|
|
212
|
+
listening = false
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
update(preferences) {
|
|
216
|
+
if (!root || !assets || !preferences.enabled || preferences.cursor !== 'prts') { dispose(); return }
|
|
217
|
+
if (listening) return
|
|
218
|
+
for (const [name, handler] of events) window.addEventListener(name, handler, true)
|
|
219
|
+
document.addEventListener('visibilitychange', visibility)
|
|
220
|
+
listening = true
|
|
221
|
+
},
|
|
222
|
+
dispose,
|
|
223
|
+
}
|
|
224
|
+
}
|
package/src/client/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
24
24
|
glass: 'standard',
|
|
25
25
|
motion: 'system',
|
|
26
26
|
bootAnimation: true,
|
|
27
|
+
cursor: 'prts',
|
|
27
28
|
conversationParticleDensity: 'sparse',
|
|
28
29
|
heroParticleDensity: 'light',
|
|
29
30
|
particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
|
|
@@ -76,7 +77,7 @@ export const PREFERENCE_GROUPS = Object.freeze({
|
|
|
76
77
|
background: Object.freeze(['texture']),
|
|
77
78
|
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
78
79
|
material: Object.freeze(['glass']),
|
|
79
|
-
accessibility: Object.freeze(['motion', 'bootAnimation']),
|
|
80
|
+
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor']),
|
|
80
81
|
navigation: Object.freeze(['railDefaultHidden']),
|
|
81
82
|
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle']),
|
|
82
83
|
})
|
|
@@ -178,6 +179,7 @@ export function normalizePreferences(value) {
|
|
|
178
179
|
glass: normalizeGlass(input),
|
|
179
180
|
motion: input.motion === 'full' ? 'system' : enumOr(input.motion, MOTIONS, DEFAULT_PREFERENCES.motion),
|
|
180
181
|
bootAnimation: booleanOr(input.bootAnimation, DEFAULT_PREFERENCES.bootAnimation),
|
|
182
|
+
cursor: input.cursor === 'system' ? 'system' : DEFAULT_PREFERENCES.cursor,
|
|
181
183
|
conversationParticleDensity: enumOr(legacyDensities.conversation, PARTICLE_DENSITIES, particlePreset.conversationParticleDensity),
|
|
182
184
|
heroParticleDensity: enumOr(legacyDensities.hero, PARTICLE_DENSITIES, particlePreset.heroParticleDensity),
|
|
183
185
|
particleTraversalSpeed: particleTraversalSpeedOr(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCursorAdapter } from './cursor-adapter.js'
|
|
1
2
|
import { normalizePreferences } from './preferences.js'
|
|
2
3
|
|
|
3
4
|
const ROOT_ATTRIBUTES = [
|
|
@@ -6,6 +7,7 @@ const ROOT_ATTRIBUTES = [
|
|
|
6
7
|
'data-prts-texture',
|
|
7
8
|
'data-prts-glass',
|
|
8
9
|
'data-prts-motion',
|
|
10
|
+
'data-prts-cursor',
|
|
9
11
|
'data-prts-particle-pattern',
|
|
10
12
|
'data-prts-session-flow',
|
|
11
13
|
'data-prts-session-glow',
|
|
@@ -67,12 +69,13 @@ function resolveScheme(value) {
|
|
|
67
69
|
return identity.includes('dark') || identity.includes('night') ? 'dark' : 'light'
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
export function createThemeController({ document, window, cssText, service, onTransitionStateChange = () => {} }) {
|
|
72
|
+
export function createThemeController({ document, window, cssText, service, cursorAssets, onTransitionStateChange = () => {} }) {
|
|
71
73
|
if (!document || !window) {
|
|
72
74
|
const gesture = { update() {}, finish() {}, cancel() {} }
|
|
73
75
|
return { apply() {}, sync() {}, refresh() {}, setTheme() {}, beginThemeTransition() { return gesture }, toggle() {}, dispose() {} }
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
const cursor = createCursorAdapter({ document, window, assets: cursorAssets })
|
|
76
79
|
const root = document.documentElement
|
|
77
80
|
let style
|
|
78
81
|
let disposed = false
|
|
@@ -115,6 +118,7 @@ export function createThemeController({ document, window, cssText, service, onTr
|
|
|
115
118
|
}
|
|
116
119
|
|
|
117
120
|
function removeOwnedState() {
|
|
121
|
+
cursor.dispose()
|
|
118
122
|
disposed = true
|
|
119
123
|
refreshRevision += 1
|
|
120
124
|
setRevision += 1
|
|
@@ -712,6 +716,7 @@ export function createThemeController({ document, window, cssText, service, onTr
|
|
|
712
716
|
disposed = false
|
|
713
717
|
const preferences = normalizePreferences(value)
|
|
714
718
|
ensureStyle()
|
|
719
|
+
cursor.update(preferences)
|
|
715
720
|
if (!preferences.enabled) {
|
|
716
721
|
cancelActiveTransition()
|
|
717
722
|
clearThemeState()
|
|
@@ -721,6 +726,7 @@ export function createThemeController({ document, window, cssText, service, onTr
|
|
|
721
726
|
root.dataset.prtsTexture = preferences.texture
|
|
722
727
|
root.dataset.prtsGlass = preferences.glass
|
|
723
728
|
root.dataset.prtsMotion = preferences.motion
|
|
729
|
+
root.dataset.prtsCursor = preferences.cursor
|
|
724
730
|
root.dataset.prtsParticlePattern = 'orthogonal'
|
|
725
731
|
root.dataset.prtsSessionFlow = preferences.sessionFlow ? 'on' : 'off'
|
|
726
732
|
root.dataset.prtsSessionGlow = preferences.sessionGlow ? 'on' : 'off'
|
|
@@ -15,6 +15,7 @@ const PRESET_OPTIONS = [
|
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
const COMMON_CONTROLS = [
|
|
18
|
+
['cursor', 'PRTS 光标', [['system', '关闭'], ['prts', '开启']]],
|
|
18
19
|
['railDefaultHidden', '默认隐藏导航栏', [['false', '关闭'], ['true', '开启']]],
|
|
19
20
|
['texture', '环境底纹', [['off', '关闭'], ['restrained', '克制'], ['full', '完整']]],
|
|
20
21
|
['glass', '玻璃材质', [['off', '关闭'], ['soft', '柔和'], ['standard', '标准'], ['clear', '清晰']]],
|
|
@@ -52,6 +53,10 @@ function settingButtons(key, label, options) {
|
|
|
52
53
|
: key === 'sessionFlowPalette'
|
|
53
54
|
? '<small data-prts-setting-note>同时适配日间与夜间模式</small>'
|
|
54
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>`
|
|
55
60
|
const sample = value => key === 'texture' || key === 'glass' || key === 'sessionFlowPalette'
|
|
56
61
|
? `<i data-prts-option-sample="${key}" data-prts-option-sample-value="${value}" aria-hidden="true"></i>`
|
|
57
62
|
: ''
|
|
@@ -113,26 +118,47 @@ export function themeSettingsMarkup() {
|
|
|
113
118
|
<button type="button" data-prts-theme-settings-close aria-label="关闭主题设置">×</button>
|
|
114
119
|
</header>
|
|
115
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>
|
|
116
128
|
<div data-prts-theme-settings-scroll>
|
|
117
|
-
<section data-prts-settings-presets>
|
|
118
|
-
<header><span><
|
|
129
|
+
<section data-prts-settings-presets data-prts-settings-section="appearance" tabindex="-1">
|
|
130
|
+
<header><span><strong>视觉预设</strong></span></header>
|
|
119
131
|
<div>${presetMarkup()}</div>
|
|
120
132
|
</section>
|
|
121
133
|
|
|
122
134
|
<section data-prts-settings-common>
|
|
123
|
-
<header><span><
|
|
124
|
-
<div data-prts-settings-common-grid
|
|
135
|
+
<header><span><strong>界面外观</strong></span></header>
|
|
136
|
+
<div data-prts-settings-common-grid>
|
|
137
|
+
${COMMON_CONTROLS.filter(([key]) => ['texture', 'glass'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
138
|
+
${settingButtons('conversationStyle', '会话主题', [['native', '原有'], ['deck-chat', '通讯链路']])}
|
|
139
|
+
</div>
|
|
140
|
+
</section>
|
|
141
|
+
<section data-prts-settings-common data-prts-settings-section="interaction" tabindex="-1">
|
|
142
|
+
<header><span><strong>交互</strong></span></header>
|
|
143
|
+
<div data-prts-settings-common-grid>
|
|
144
|
+
${COMMON_CONTROLS.filter(([key]) => ['cursor', 'railDefaultHidden', 'motion'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
145
|
+
</div>
|
|
146
|
+
</section>
|
|
147
|
+
<section data-prts-settings-common data-prts-settings-section="flow" tabindex="-1">
|
|
148
|
+
<header><span><strong>会话流线</strong></span></header>
|
|
149
|
+
<div data-prts-settings-common-grid>
|
|
150
|
+
${COMMON_CONTROLS.filter(([key]) => ['sessionFlow', 'sessionGlow', 'sessionFlowPalette'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
125
151
|
${sessionFlowColorMarkup()}
|
|
126
152
|
<label data-prts-setting-row="sessionFlowSpeed" data-prts-setting-range-row data-prts-session-flow-speed>
|
|
127
153
|
<span><strong>流线速度</strong><small data-prts-setting-note>控制当前会话流线的形变速度</small></span>
|
|
128
154
|
<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>
|
|
129
155
|
</label>
|
|
130
|
-
|
|
156
|
+
|
|
131
157
|
</div>
|
|
132
158
|
</section>
|
|
133
159
|
|
|
134
|
-
<details data-prts-settings-advanced>
|
|
135
|
-
<summary><span><
|
|
160
|
+
<details data-prts-settings-advanced data-prts-settings-section="particles" tabindex="-1">
|
|
161
|
+
<summary><span><strong>粒子效果</strong></span><output data-prts-advanced-summary>标准 / 1×</output></summary>
|
|
136
162
|
<div data-prts-settings-advanced-content>
|
|
137
163
|
<section data-prts-particle-detail>
|
|
138
164
|
<header><strong>粒子精度</strong><span>同时调整会话与新会话徽记</span></header>
|
|
@@ -162,6 +188,7 @@ export function themeSettingsMarkup() {
|
|
|
162
188
|
</div>
|
|
163
189
|
</footer>
|
|
164
190
|
</div>
|
|
191
|
+
</div>
|
|
165
192
|
</div>
|
|
166
193
|
</dialog>`
|
|
167
194
|
}
|
|
@@ -428,6 +455,18 @@ export function createThemeSettingsOverlay({
|
|
|
428
455
|
|
|
429
456
|
function renderState() {
|
|
430
457
|
if (!panel || !currentPreferences) return
|
|
458
|
+
for (const toggle of panel.querySelectorAll('[data-prts-setting-switch]')) {
|
|
459
|
+
const key = toggle.dataset.prtsSettingSwitch
|
|
460
|
+
const enabled = key === 'cursor' ? currentPreferences.cursor === 'prts'
|
|
461
|
+
: key === 'railDefaultHidden' ? currentPreferences[key] === true : currentPreferences[key] !== false
|
|
462
|
+
toggle.setAttribute('aria-checked', String(enabled))
|
|
463
|
+
toggle.classList.toggle('is-active', enabled)
|
|
464
|
+
}
|
|
465
|
+
for (const key of ['sessionGlow', 'sessionFlowPalette', 'sessionFlowColors', 'sessionFlowSpeed']) {
|
|
466
|
+
const row = panel.querySelector(`[data-prts-setting-row="${key}"]`)
|
|
467
|
+
if (row) row.hidden = currentPreferences.sessionFlow === false
|
|
468
|
+
|| (key === 'sessionFlowColors' && activeSessionFlowPalette() !== 'custom')
|
|
469
|
+
}
|
|
431
470
|
const particleDetail = resolveParticleDetail(currentPreferences)
|
|
432
471
|
for (const button of panel.querySelectorAll('button[data-prts-setting-key]')) {
|
|
433
472
|
const key = button.dataset.prtsSettingKey
|
|
@@ -481,7 +520,23 @@ export function createThemeSettingsOverlay({
|
|
|
481
520
|
if (event.target === panel) { setOpen(false); return }
|
|
482
521
|
const target = event.target?.closest?.('button')
|
|
483
522
|
if (!target) return
|
|
523
|
+
if (target.hasAttribute('data-prts-settings-jump')) {
|
|
524
|
+
const section = panel.querySelector(`[data-prts-settings-section="${target.dataset.prtsSettingsJump}"]`)
|
|
525
|
+
const scroller = panel.querySelector('[data-prts-theme-settings-scroll]')
|
|
526
|
+
if (section && scroller) {
|
|
527
|
+
if (section.tagName === 'DETAILS') section.open = true
|
|
528
|
+
scroller.scrollTo?.({ top: target.dataset.prtsSettingsJump === 'appearance' ? 0 : scroller.scrollTop + section.getBoundingClientRect().top - scroller.getBoundingClientRect().top, behavior: 'instant' })
|
|
529
|
+
section.focus?.({ preventScroll: true })
|
|
530
|
+
}
|
|
531
|
+
return
|
|
532
|
+
}
|
|
484
533
|
if (target.hasAttribute('data-prts-theme-settings-close')) { setOpen(false); return }
|
|
534
|
+
if (target.hasAttribute('data-prts-setting-switch')) {
|
|
535
|
+
const key = target.dataset.prtsSettingSwitch
|
|
536
|
+
const enabled = target.getAttribute('aria-checked') === 'true'
|
|
537
|
+
onPreferenceChange(key, key === 'cursor' ? (enabled ? 'system' : 'prts') : !enabled)
|
|
538
|
+
return
|
|
539
|
+
}
|
|
485
540
|
if (target.hasAttribute('data-prts-setting-key')) {
|
|
486
541
|
const raw = target.dataset.prtsSettingValue
|
|
487
542
|
if (target.dataset.prtsSettingKey === 'sessionFlowPalette') {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Native cursor; gesture frames override the resting image without a DOM overlay. */
|
|
2
|
+
@media (any-pointer: fine) {
|
|
3
|
+
html[data-dsh-prts][data-prts-cursor="prts"] {
|
|
4
|
+
--prts-hand-cursor: var(--prts-hand-frame, url("__PRTS_HAND__") 7 6);
|
|
5
|
+
--prts-text-cursor: var(--prts-text-frame, url("__PRTS_TEXT__") 24 24);
|
|
6
|
+
--prts-link-cursor: url("__PRTS_LINK__") 7 7;
|
|
7
|
+
cursor: var(--prts-hand-cursor), default;
|
|
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"]) {
|
|
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;
|
|
14
|
+
}
|
|
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"]) {
|
|
16
|
+
cursor: var(--prts-text-cursor), text;
|
|
17
|
+
}
|
|
18
|
+
html[data-dsh-prts][data-prts-cursor="prts"] :is(:disabled, [aria-disabled="true"]) {
|
|
19
|
+
cursor: not-allowed !important;
|
|
20
|
+
}
|
|
21
|
+
}
|