@ash-qw/dsh-theme-prts 2.5.3 → 2.5.4
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 +14 -0
- package/lib/client.js +148 -34
- package/package.json +1 -1
- package/src/client/background-layout.js +21 -7
- package/src/client/conversation-control-adapter.js +35 -10
- package/src/client/cursor-adapter.js +4 -3
- package/src/client/host-geometry-adapter.js +11 -5
- package/src/client/index.js +1 -0
- package/src/client/preferences.js +9 -1
- package/src/client/sidebar-control-adapter.js +38 -1
- package/src/client/theme-controller-v2.js +1 -0
- package/src/client/theme-controller.js +1 -0
- package/src/client/theme-settings-workbench.js +9 -0
- package/src/client/theme-transfer-panel.js +2 -3
- package/src/styles/custom-background.css +2 -1
- package/src/styles/facility-vector.css +5 -2
- package/src/styles/refinement.css +1 -1
- package/src/styles/theme-settings-workbench.css +14 -1
- package/src/vendor/react-bits/GlassSurface.jsx +16 -2
package/package.json
CHANGED
|
@@ -5,7 +5,16 @@ export function createBackgroundLayout({window,document}) {
|
|
|
5
5
|
const raf=window.requestAnimationFrame?.bind(window) ?? (fn=>window.setTimeout(fn,16))
|
|
6
6
|
const caf=window.cancelAnimationFrame?.bind(window) ?? window.clearTimeout.bind(window)
|
|
7
7
|
let state={}, frame, disposed=false, enabled=false, observing=false, hostFrame, operation, sessions
|
|
8
|
-
const
|
|
8
|
+
const sidebarTargets = new Set()
|
|
9
|
+
const set=(name,value)=>{
|
|
10
|
+
const targets=name.startsWith('--prts-protected-sidebar-')?[sessions,document.querySelector('[data-prts-nav-rail]')]:[root]
|
|
11
|
+
for(const node of targets){
|
|
12
|
+
if(!node)continue
|
|
13
|
+
if(node!==root)sidebarTargets.add(node)
|
|
14
|
+
if(value===null)node.style.removeProperty(name)
|
|
15
|
+
else if(node.style.getPropertyValue(name)!==value)node.style.setProperty(name,value)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
9
18
|
const update=()=>{
|
|
10
19
|
frame=undefined
|
|
11
20
|
if(disposed||!observing)return
|
|
@@ -17,6 +26,8 @@ export function createBackgroundLayout({window,document}) {
|
|
|
17
26
|
for(const node of [hostFrame||operation,sessions])if(node)resize?.observe(node)
|
|
18
27
|
}
|
|
19
28
|
const railWidth=parseFloat(window.getComputedStyle(root).getPropertyValue('--prts-nav-width'))||0
|
|
29
|
+
const writes=[]
|
|
30
|
+
const write=(...args)=>writes.push(args)
|
|
20
31
|
for(const slot of ['full','conversation','sidebar']){
|
|
21
32
|
const entry=state[slot]
|
|
22
33
|
const node=slot==='sidebar'?sessions:slot==='conversation'?(hostFrame||operation):operation
|
|
@@ -25,15 +36,16 @@ export function createBackgroundLayout({window,document}) {
|
|
|
25
36
|
const active=entry?.region && width>0 && height>0
|
|
26
37
|
if(active){
|
|
27
38
|
const p=backgroundPlacement(entry.width,entry.height,width,height,entry.region)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if(slot==='sidebar')
|
|
39
|
+
write(`--prts-protected-${slot}-x`,`${p.x}px`)
|
|
40
|
+
write(`--prts-protected-${slot}-y`,`${p.y}px`)
|
|
41
|
+
if(slot==='sidebar')write('--prts-protected-sidebar-width',`${p.width}px`)
|
|
31
42
|
}else{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if(slot==='sidebar')
|
|
43
|
+
write(`--prts-protected-${slot}-x`,null)
|
|
44
|
+
write(`--prts-protected-${slot}-y`,null)
|
|
45
|
+
if(slot==='sidebar')write('--prts-protected-sidebar-width',null)
|
|
35
46
|
}
|
|
36
47
|
}
|
|
48
|
+
for(const args of writes)set(...args)
|
|
37
49
|
}
|
|
38
50
|
function schedule(){if(!disposed && observing && frame===undefined)frame=raf(update)}
|
|
39
51
|
// ResizeObserver runs after layout, before paint. Deferring again to rAF
|
|
@@ -48,6 +60,8 @@ export function createBackgroundLayout({window,document}) {
|
|
|
48
60
|
const attributes=new window.MutationObserver(schedule)
|
|
49
61
|
function clear(){
|
|
50
62
|
caf(frame);frame=undefined;resize?.disconnect();mutations.disconnect();attributes.disconnect();window.removeEventListener('resize',schedule)
|
|
63
|
+
for(const node of sidebarTargets)for(const axis of ['x','y','width'])node.style.removeProperty(`--prts-protected-sidebar-${axis}`)
|
|
64
|
+
sidebarTargets.clear()
|
|
51
65
|
hostFrame=undefined;operation=undefined;sessions=undefined;observing=false
|
|
52
66
|
for(const slot of ['full','conversation','sidebar'])for(const axis of ['x','y','width'])root.style.removeProperty(`--prts-protected-${slot}-${axis}`)
|
|
53
67
|
}
|
|
@@ -49,6 +49,8 @@ function classify(button, operation) {
|
|
|
49
49
|
if (approval) return hasClassSuffix(button, '_reject') ? 'danger' : 'cta'
|
|
50
50
|
if (hasClassSuffix(button, '_modalAction') || button.closest('[role="dialog"]')) return 'cta'
|
|
51
51
|
|
|
52
|
+
const userRow = closestWithClassSuffix(button, '_userRow')
|
|
53
|
+
if (userRow?.matches('[data-submission-echo], [data-pending-steering]')) return 'micro'
|
|
52
54
|
if (button.closest('[data-chat-flow-kind], [data-message-role]')) return 'micro'
|
|
53
55
|
return null
|
|
54
56
|
}
|
|
@@ -129,7 +131,7 @@ export function createConversationControlAdapter({ document, window }) {
|
|
|
129
131
|
subtree: true,
|
|
130
132
|
childList: true,
|
|
131
133
|
attributes: true,
|
|
132
|
-
attributeFilter: ['class', 'data-approval-key', 'data-turn-process'],
|
|
134
|
+
attributeFilter: ['class', 'data-approval-key', 'data-turn-process', 'data-submission-echo', 'data-pending-steering', 'data-chat-flow-kind', 'data-message-role'],
|
|
133
135
|
})
|
|
134
136
|
},
|
|
135
137
|
dispose() {
|
|
@@ -166,7 +168,15 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
166
168
|
}
|
|
167
169
|
let target=null, hovered=null, focused=null, raf=0, pressed=false, releasePending=false, disposed=false
|
|
168
170
|
let pointer={x:0,y:0}, state=null, settled=false, tween=null, bounds=null, boundsDirty=false, hitDirty=false, exitGoal=null, motionDuration=240, targetArm=7, retiringBounds=null, targetRetires=false
|
|
169
|
-
|
|
171
|
+
let moveRaf=0, pendingMove=null
|
|
172
|
+
const resizeObserver=window.ResizeObserver?new window.ResizeObserver(entries=>{
|
|
173
|
+
if(!target)return
|
|
174
|
+
const entry=entries.find(item=>item.target===target),size=entry?.borderBoxSize?.[0]
|
|
175
|
+
if(size && bounds && Math.abs(size.inlineSize-bounds.width)<.5 && Math.abs(size.blockSize-bounds.height)<.5)return
|
|
176
|
+
const box=target.getBoundingClientRect()
|
|
177
|
+
if(bounds && ['left','top','width','height'].every(key=>bounds[key]===box[key]))return
|
|
178
|
+
boundsDirty=true;wake()
|
|
179
|
+
}):null
|
|
170
180
|
const reduced=window.matchMedia?.('(prefers-reduced-motion: reduce)')
|
|
171
181
|
const fine=window.matchMedia?.('(hover:hover) and (pointer:fine)')
|
|
172
182
|
let activeModal=null
|
|
@@ -179,11 +189,12 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
179
189
|
const candidate=card || pendingAction || node.closest(selector)
|
|
180
190
|
return candidate && !candidate.matches(':disabled,[aria-disabled="true"]') && !candidate.closest('[inert]') ? candidate : null
|
|
181
191
|
}
|
|
182
|
-
const measure=()=>{bounds=target.getBoundingClientRect();targetArm=7+Math.max(0,Math.min(1,(Math.min(bounds.width,bounds.height)-36)/36))*5;boundsDirty=false}
|
|
192
|
+
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}
|
|
183
193
|
const wake=()=>{if(!raf && !disposed)raf=window.requestAnimationFrame(draw)}
|
|
184
194
|
const choose=()=>{
|
|
185
195
|
const next=(hovered?.isConnected?hovered:null) || (focused?.isConnected?focused:null)
|
|
186
196
|
if(next!==target){
|
|
197
|
+
const nextBounds=next?.getBoundingClientRect()
|
|
187
198
|
const retire=!next && target && !target.isConnected && targetRetires && releasePending && bounds && !reduced?.matches
|
|
188
199
|
imageRetiring=false
|
|
189
200
|
retiringBounds=retire?{left:bounds.left,top:bounds.top,width:bounds.width,height:bounds.height}:null
|
|
@@ -195,12 +206,13 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
195
206
|
if(target){
|
|
196
207
|
targetRetires=hasClassSuffix(target,'_toBottom') || isPendingInteractionAction(target)
|
|
197
208
|
target.setAttribute('data-prts-corner-target','');showFrame()
|
|
198
|
-
measure();const box=bounds
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
frame.style.color=
|
|
209
|
+
measure(nextBounds);const box=bounds
|
|
210
|
+
state={x:(hovered?pointer.x:box.left+box.width/2)-10,y:(hovered?pointer.y:box.top+box.height/2)-10,w:20,h:20,alpha:0,arm:7}
|
|
211
|
+
frame.style.opacity='0'
|
|
212
|
+
frame.style.color=target.getAttribute('data-prts-conversation-control')==='danger'?window.getComputedStyle(target).getPropertyValue('--prts-red'):'var(--prts-corner-color, var(--prts-yellow))'
|
|
202
213
|
}
|
|
203
214
|
}
|
|
215
|
+
if(!target && !state?.alpha)hideFrame()
|
|
204
216
|
if(!settled || !target && state?.alpha>0)wake()
|
|
205
217
|
}
|
|
206
218
|
function draw(time) {
|
|
@@ -227,7 +239,7 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
227
239
|
tween={from:{...state},to:{...goal},start:time,duration:remaining,pressed}
|
|
228
240
|
}
|
|
229
241
|
// All channels share one endpoint; no separate thresholds or late position snap.
|
|
230
|
-
const progress=reduced?.matches || settled || !tween.duration?1:Math.min(1,(time-tween.start)/tween.duration)
|
|
242
|
+
const progress=reduced?.matches || settled || !tween.duration || (!state.alpha && !goal.alpha)?1:Math.min(1,(time-tween.start)/tween.duration)
|
|
231
243
|
const ease=1-(1-progress)**3
|
|
232
244
|
for(const key of keys)state[key]=tween.from[key]+(goal[key]-tween.from[key])*ease
|
|
233
245
|
const moving=progress<1
|
|
@@ -251,17 +263,28 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
251
263
|
else if(moving)wake()
|
|
252
264
|
else {tween=null;if(!state.alpha)hideFrame()}
|
|
253
265
|
}
|
|
254
|
-
const
|
|
266
|
+
const applyMove=event=>{
|
|
255
267
|
if(document.hidden || event.pointerType==='touch' || !fine?.matches)return
|
|
256
268
|
pointer={x:event.clientX,y:event.clientY}
|
|
257
269
|
const next=find(event.target)
|
|
258
270
|
if(next===hovered && (next || !state?.alpha))return
|
|
259
271
|
hovered=next;choose()
|
|
260
272
|
}
|
|
261
|
-
const
|
|
273
|
+
const clearMove=()=>{window.cancelAnimationFrame(moveRaf);moveRaf=0;pendingMove=null}
|
|
274
|
+
const flushMove=()=>{
|
|
275
|
+
const event=pendingMove;clearMove()
|
|
276
|
+
if(event)applyMove(event)
|
|
277
|
+
}
|
|
278
|
+
const move=event=>{
|
|
279
|
+
if(document.hidden || event.pointerType==='touch' || !fine?.matches)return
|
|
280
|
+
pendingMove={target:event.target,clientX:event.clientX,clientY:event.clientY,pointerType:event.pointerType}
|
|
281
|
+
if(!moveRaf)moveRaf=window.requestAnimationFrame(flushMove)
|
|
282
|
+
}
|
|
283
|
+
const leave=()=>{clearMove();hovered=null;choose()}
|
|
262
284
|
const focus=event=>{focused=event.target.matches?.(':focus-visible')?find(event.target):null;choose()}
|
|
263
285
|
const blur=()=>{focused=null;choose()}
|
|
264
286
|
const down=event=>{
|
|
287
|
+
flushMove()
|
|
265
288
|
pointer={x:event.clientX,y:event.clientY}
|
|
266
289
|
if(event.pointerType==='touch')hovered=find(event.target)
|
|
267
290
|
choose()
|
|
@@ -275,6 +298,7 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
275
298
|
}
|
|
276
299
|
const up=event=>{release();if(event.pointerType==='touch')hovered=null;choose()}
|
|
277
300
|
const reset=()=>{
|
|
301
|
+
clearMove()
|
|
278
302
|
target?.removeAttribute('data-prts-corner-target');hideFrame()
|
|
279
303
|
hovered=focused=target=null;state=tween=bounds=exitGoal=retiringBounds=null
|
|
280
304
|
imageRetiring=false
|
|
@@ -317,6 +341,7 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
317
341
|
]
|
|
318
342
|
for(const [node,event,handler] of listeners)node.addEventListener(event,handler)
|
|
319
343
|
return ()=>{
|
|
344
|
+
clearMove()
|
|
320
345
|
modalObserver?.disconnect();document.removeEventListener('toggle',modalToggle,true)
|
|
321
346
|
disposed=true;target?.removeAttribute('data-prts-corner-target');hideFrame();window.cancelAnimationFrame(raf);resizeObserver?.disconnect()
|
|
322
347
|
document.removeEventListener('scroll',scroll,true);window.removeEventListener('resize',reset)
|
|
@@ -8,7 +8,8 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
8
8
|
let gestureAssets = assets
|
|
9
9
|
let textTarget
|
|
10
10
|
let lastPoint
|
|
11
|
-
const
|
|
11
|
+
const heroTextSelector = '[data-phase="hero"] [class$="_titleGroup"]'
|
|
12
|
+
const contentSelector = '[data-markdown], [data-slot*="markdown" i], [class*="markdown" i], :is([data-chat-flow-kind="user"], [data-chat-flow-kind="steering"], [data-submission-echo], [data-pending-steering]) [class$="_bubble"]'
|
|
12
13
|
const controlSelector = 'a[href], button, summary, input, textarea, select, [contenteditable], [role="button"], [role="toolbar"], [role="tab"]'
|
|
13
14
|
function clearTextHit() {
|
|
14
15
|
textTarget?.removeAttribute('data-prts-text-hit')
|
|
@@ -25,8 +26,8 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
25
26
|
if (event.pointerType !== 'mouse') return
|
|
26
27
|
lastPoint = { clientX: event.clientX, clientY: event.clientY, pointerType: 'mouse' }
|
|
27
28
|
const target = event.target?.nodeType === 1 ? event.target : event.target?.parentElement
|
|
28
|
-
const content = target?.closest?.(contentSelector)
|
|
29
|
-
if (!content
|
|
29
|
+
const content = target?.closest?.(`${contentSelector}, ${heroTextSelector}`)
|
|
30
|
+
if (!content || (!content.matches(heroTextSelector) && !content.closest('[data-chat-flow-kind], [data-message-role], [data-submission-echo], [data-pending-steering]')) || target.closest(controlSelector)) return
|
|
30
31
|
const style = window.getComputedStyle(target)
|
|
31
32
|
if (style.userSelect === 'none' || !style.cursor.includes('data:image/png')) return
|
|
32
33
|
// Caret APIs return the nearest insertion point even over empty space.
|
|
@@ -17,7 +17,7 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
17
17
|
const root = document?.documentElement
|
|
18
18
|
let frame
|
|
19
19
|
let sidebar
|
|
20
|
-
|
|
20
|
+
const sidebarWidths = new Map()
|
|
21
21
|
let resizeObserver
|
|
22
22
|
let frameRequest
|
|
23
23
|
let resizeSettleTimer
|
|
@@ -35,7 +35,11 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
35
35
|
}
|
|
36
36
|
if (sidebar?.isConnected) {
|
|
37
37
|
const width = cssPixels(sidebar.getBoundingClientRect().width)
|
|
38
|
-
|
|
38
|
+
for (const node of [sidebar, document.querySelector('[data-prts-nav-rail]')]) {
|
|
39
|
+
if (!node) continue
|
|
40
|
+
if (!sidebarWidths.has(node)) sidebarWidths.set(node, [node.style.getPropertyValue('--prts-sidebar-width'), node.style.getPropertyPriority('--prts-sidebar-width')])
|
|
41
|
+
if (node.style.getPropertyValue('--prts-sidebar-width') !== width) node.style.setProperty('--prts-sidebar-width', width)
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
return top
|
|
41
45
|
}
|
|
@@ -103,8 +107,11 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
103
107
|
else root.style.removeProperty(TOP_INSET_PROPERTY)
|
|
104
108
|
previousValue = undefined
|
|
105
109
|
previousPriority = undefined
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
for (const [node, [value, priority]] of sidebarWidths) {
|
|
111
|
+
if (value) node.style.setProperty('--prts-sidebar-width', value, priority)
|
|
112
|
+
else node.style.removeProperty('--prts-sidebar-width')
|
|
113
|
+
}
|
|
114
|
+
sidebarWidths.clear()
|
|
108
115
|
sidebar = undefined
|
|
109
116
|
frame = undefined
|
|
110
117
|
observedFrameHeight = undefined
|
|
@@ -121,7 +128,6 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
121
128
|
if (started) dispose()
|
|
122
129
|
frame = nextFrame
|
|
123
130
|
sidebar = frame.querySelector?.('[data-prts-region="sessions"]')
|
|
124
|
-
previousSidebarWidth = root.style.getPropertyValue('--prts-sidebar-width')
|
|
125
131
|
previousValue = root.style.getPropertyValue(TOP_INSET_PROPERTY)
|
|
126
132
|
previousPriority = root.style.getPropertyPriority(TOP_INSET_PROPERTY)
|
|
127
133
|
started = true
|
package/src/client/index.js
CHANGED
|
@@ -223,6 +223,7 @@ 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
227
|
if (key === 'particleTraversalSpeed') {
|
|
227
228
|
particleField.update(updatePreferenceValue(preferences, key, value))
|
|
228
229
|
return
|
|
@@ -39,6 +39,8 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
39
39
|
bakerBlur: 8,
|
|
40
40
|
classicUserBlur: 8,
|
|
41
41
|
cursor: 'prts',
|
|
42
|
+
cornerColor: '#f0c800',
|
|
43
|
+
cornerCustomColor: '#f0c800',
|
|
42
44
|
conversationParticleDensity: 'sparse',
|
|
43
45
|
heroParticleDensity: 'light',
|
|
44
46
|
particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
|
|
@@ -105,7 +107,7 @@ export const PREFERENCE_GROUPS = Object.freeze({
|
|
|
105
107
|
background: Object.freeze(['texture', 'backgroundMode', 'backgroundDim', 'heroContrast', 'sidebarOpacity', 'rightSidebarOpacity', 'railOpacity', 'bakerOpacity', 'classicUserOpacity', 'sidebarBlur', 'rightSidebarBlur', 'railBlur', 'bakerBlur', 'classicUserBlur']),
|
|
106
108
|
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
107
109
|
material: Object.freeze(['glass', 'composerGlass']),
|
|
108
|
-
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor']),
|
|
110
|
+
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor', 'cornerColor', 'cornerCustomColor']),
|
|
109
111
|
aiTools: Object.freeze(['aiToolsEnabled']),
|
|
110
112
|
navigation: Object.freeze(['railDefaultHidden', 'sessionPreview', 'previewOngoing', 'previewTools', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']),
|
|
111
113
|
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle', 'bakerGroupAvatars', 'bakerLoading', 'conversationEmblem']),
|
|
@@ -229,6 +231,8 @@ export function normalizePreferences(value) {
|
|
|
229
231
|
backgroundDim: typeof input.backgroundDim === 'number' && Number.isFinite(input.backgroundDim) ? Math.min(.9, Math.max(0, input.backgroundDim)) : .45,
|
|
230
232
|
composerGlass: booleanOr(input.composerGlass, DEFAULT_PREFERENCES.composerGlass),
|
|
231
233
|
bootAnimation: booleanOr(input.bootAnimation, DEFAULT_PREFERENCES.bootAnimation),
|
|
234
|
+
cornerCustomColor: colorOr(input.cornerCustomColor, DEFAULT_PREFERENCES.cornerCustomColor),
|
|
235
|
+
cornerColor: colorOr(input.cornerColor, DEFAULT_PREFERENCES.cornerColor),
|
|
232
236
|
cursor: input.cursor === 'system' ? 'system' : DEFAULT_PREFERENCES.cursor,
|
|
233
237
|
conversationParticleDensity: enumOr(legacyDensities.conversation, PARTICLE_DENSITIES, particlePreset.conversationParticleDensity),
|
|
234
238
|
heroParticleDensity: enumOr(legacyDensities.hero, PARTICLE_DENSITIES, particlePreset.heroParticleDensity),
|
|
@@ -283,6 +287,10 @@ export function updatePreferenceValue(value, key, nextValue) {
|
|
|
283
287
|
if (key === 'preset') return applyVisualPreset(value, nextValue)
|
|
284
288
|
if (key === 'particleDetail') return applyParticleDetail(value, nextValue)
|
|
285
289
|
const current = normalizePreferences(value)
|
|
290
|
+
if (key === 'cornerCustomColor') {
|
|
291
|
+
const color = colorOr(nextValue, current.cornerCustomColor)
|
|
292
|
+
return normalizePreferences({ ...current, cornerColor: color, cornerCustomColor: color })
|
|
293
|
+
}
|
|
286
294
|
if (key === 'sessionFlowPalette') {
|
|
287
295
|
const palette = enumOr(nextValue, SESSION_FLOW_PALETTE_IDS, current.sessionFlowPalette)
|
|
288
296
|
const colors = SESSION_FLOW_PALETTES[palette]
|
|
@@ -862,6 +862,28 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
862
862
|
}
|
|
863
863
|
|
|
864
864
|
|
|
865
|
+
let pickupRow, pickupTimer, pickupRoot
|
|
866
|
+
function clearPickup() {
|
|
867
|
+
if (pickupTimer !== undefined) window.clearTimeout(pickupTimer)
|
|
868
|
+
pickupTimer = undefined
|
|
869
|
+
pickupRow?.removeAttribute('data-prts-pickup-ready')
|
|
870
|
+
pickupRow = undefined
|
|
871
|
+
}
|
|
872
|
+
function pickupOver(event) {
|
|
873
|
+
const row = event.target.closest?.('[data-prts-session-row]')
|
|
874
|
+
if (row === pickupRow) return
|
|
875
|
+
clearPickup()
|
|
876
|
+
if (!row) return
|
|
877
|
+
pickupRow = row
|
|
878
|
+
pickupTimer = window.setTimeout(() => {
|
|
879
|
+
pickupTimer = undefined
|
|
880
|
+
if (row.isConnected && pickupRow === row) row.setAttribute('data-prts-pickup-ready', '')
|
|
881
|
+
}, 100)
|
|
882
|
+
}
|
|
883
|
+
function pickupOut(event) {
|
|
884
|
+
if (pickupRow && !pickupRow.contains(event.relatedTarget)) clearPickup()
|
|
885
|
+
}
|
|
886
|
+
|
|
865
887
|
function queueScan() {
|
|
866
888
|
if (!adapterActive || scanFrame !== undefined) return
|
|
867
889
|
const requestFrame = window?.requestAnimationFrame?.bind(window) ?? (callback => window?.setTimeout?.(callback, 0))
|
|
@@ -1021,8 +1043,18 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
1021
1043
|
if (observer || !window?.MutationObserver) return
|
|
1022
1044
|
const observationRoot = document.querySelector('[data-prts-region="sessions"]')
|
|
1023
1045
|
if (!observationRoot) return
|
|
1046
|
+
pickupRoot = observationRoot
|
|
1047
|
+
pickupRoot.addEventListener('pointerover', pickupOver)
|
|
1048
|
+
pickupRoot.addEventListener('pointerout', pickupOut)
|
|
1024
1049
|
observer = new window.MutationObserver(mutations => {
|
|
1025
|
-
const relevant = mutations.filter(mutation =>
|
|
1050
|
+
const relevant = mutations.filter(mutation => {
|
|
1051
|
+
if (mutation.target.closest?.('[data-prts-owned-session-summary]')) return false
|
|
1052
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'class' && !mutation.target.closest('[role="treeitem"]')) {
|
|
1053
|
+
const stableClasses = value => (value ?? '').split(/\s+/).filter(token => token && !token.endsWith('_quietBars')).sort().join(' ')
|
|
1054
|
+
if (stableClasses(mutation.oldValue) === stableClasses(mutation.target.getAttribute('class'))) return false
|
|
1055
|
+
}
|
|
1056
|
+
return true
|
|
1057
|
+
})
|
|
1026
1058
|
const freshRow = 'div[role="treeitem"]:not([data-prts-session-row]):not([data-prts-workspace-row])'
|
|
1027
1059
|
const mountsRows = relevant.some(mutation => mutation.type === 'childList'
|
|
1028
1060
|
&& Array.from(mutation.addedNodes).some(node => node.nodeType === 1
|
|
@@ -1040,9 +1072,14 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
1040
1072
|
childList: true,
|
|
1041
1073
|
attributes: true,
|
|
1042
1074
|
attributeFilter: ['aria-expanded', 'aria-selected', 'class', 'data-state'],
|
|
1075
|
+
attributeOldValue: true,
|
|
1043
1076
|
})
|
|
1044
1077
|
},
|
|
1045
1078
|
dispose() {
|
|
1079
|
+
clearPickup()
|
|
1080
|
+
pickupRoot?.removeEventListener('pointerover', pickupOver)
|
|
1081
|
+
pickupRoot?.removeEventListener('pointerout', pickupOut)
|
|
1082
|
+
pickupRoot = undefined
|
|
1046
1083
|
adapterActive = false
|
|
1047
1084
|
disconnectSummarySources()
|
|
1048
1085
|
cancelReveal()
|
|
@@ -19,6 +19,7 @@ const ROOT_ATTRIBUTES = [
|
|
|
19
19
|
'data-prts-background-mode',
|
|
20
20
|
]
|
|
21
21
|
const SESSION_FLOW_COLOR_PROPERTIES = {
|
|
22
|
+
'--prts-corner-color': 'cornerColor',
|
|
22
23
|
'--prts-session-flow-back': 'sessionFlowColorBack',
|
|
23
24
|
'--prts-session-flow-core': 'sessionFlowColorCore',
|
|
24
25
|
'--prts-session-flow-front': 'sessionFlowColorFront',
|
|
@@ -14,6 +14,7 @@ const ROOT_ATTRIBUTES = [
|
|
|
14
14
|
'data-prts-conversation-style',
|
|
15
15
|
]
|
|
16
16
|
const SESSION_FLOW_COLOR_PROPERTIES = {
|
|
17
|
+
'--prts-corner-color': 'cornerColor',
|
|
17
18
|
'--prts-session-flow-back': 'sessionFlowColorBack',
|
|
18
19
|
'--prts-session-flow-core': 'sessionFlowColorCore',
|
|
19
20
|
'--prts-session-flow-front': 'sessionFlowColorFront',
|
|
@@ -171,6 +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
175
|
</div>
|
|
175
176
|
</section>
|
|
176
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>
|
|
@@ -531,6 +532,10 @@ export function createThemeSettingsOverlay({
|
|
|
531
532
|
|
|
532
533
|
function renderState() {
|
|
533
534
|
if (!panel || !currentPreferences) return
|
|
535
|
+
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))
|
|
534
539
|
for (const toggle of panel.querySelectorAll('[data-prts-setting-switch]')) {
|
|
535
540
|
const key = toggle.dataset.prtsSettingSwitch
|
|
536
541
|
const enabled = key === 'cursor' ? currentPreferences.cursor === 'prts'
|
|
@@ -625,6 +630,8 @@ export function createThemeSettingsOverlay({
|
|
|
625
630
|
if (event.target === panel) { setOpen(false); return }
|
|
626
631
|
const target = event.target?.closest?.('button')
|
|
627
632
|
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 }
|
|
628
635
|
if (target.hasAttribute('data-prts-settings-jump')) {
|
|
629
636
|
selectSection(target.dataset.prtsSettingsJump)
|
|
630
637
|
return
|
|
@@ -663,6 +670,7 @@ export function createThemeSettingsOverlay({
|
|
|
663
670
|
|
|
664
671
|
function onPanelInput(event) {
|
|
665
672
|
const target = event.target
|
|
673
|
+
if (target?.matches?.('[data-prts-corner-color]')) { onPreferencePreview('cornerColor', target.value); return }
|
|
666
674
|
if (target?.matches?.('[data-prts-preview-reply-limit]')) { onPreferenceChange('previewReplyLimit', Number(target.value)); return }
|
|
667
675
|
if (target?.matches?.('input[data-prts-setting-color]')) {
|
|
668
676
|
const key = target.dataset.prtsSettingKey
|
|
@@ -725,6 +733,7 @@ export function createThemeSettingsOverlay({
|
|
|
725
733
|
const renderMediaState = () => renderState()
|
|
726
734
|
const commitFromControl = event => {
|
|
727
735
|
const target = event.target
|
|
736
|
+
if (target?.matches?.('[data-prts-corner-color]')) { if (event.type === 'change') onPreferenceChange('cornerCustomColor', target.value); return }
|
|
728
737
|
if (target?.matches?.('input[data-prts-setting-color]')) {
|
|
729
738
|
if (event.type === 'change' || event.type === 'focusout') commitSessionFlowColor(target.dataset.prtsSettingKey)
|
|
730
739
|
return
|
|
@@ -13,9 +13,8 @@ export function themeTransferMarkup() {
|
|
|
13
13
|
<button type="button" data-prts-transfer-export>导出主题文件 <span aria-hidden="true">↓</span></button>
|
|
14
14
|
</section>
|
|
15
15
|
<section data-prts-transfer-card="import" data-prts-transfer-drop>
|
|
16
|
-
<h3>导入主题</h3><p
|
|
17
|
-
<
|
|
18
|
-
<button type="button" data-prts-transfer-choose>选择主题文件 <span aria-hidden="true">↑</span></button>
|
|
16
|
+
<h3>导入主题</h3><p>导入已导出的主题文件,确认内容后应用。</p>
|
|
17
|
+
<button type="button" data-prts-transfer-file-hint data-prts-transfer-choose><span aria-hidden="true">↑</span><strong>点击选择或拖拽主题文件到此处</strong><small>.prts-theme.json · 最大 160 MB</small></button>
|
|
19
18
|
<input type="file" accept=".prts-theme.json,application/json" aria-label="导入主题文件" data-prts-transfer-file hidden>
|
|
20
19
|
</section>
|
|
21
20
|
</div>
|
|
@@ -5,9 +5,10 @@ background: linear-gradient(var(--prts-background-wash), var(--prts-background-w
|
|
|
5
5
|
}
|
|
6
6
|
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] :is([data-prts-region="sessions"], [data-prts-region="operation"]) { background: transparent; }
|
|
7
7
|
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-sidebar] {
|
|
8
|
+
& :is([data-prts-region="sessions"],[data-prts-nav-rail]) {
|
|
8
9
|
--prts-wall-width:var(--prts-protected-sidebar-width,max(calc(var(--prts-sidebar-width,280px) + var(--prts-nav-width)),calc((100dvh - var(--prts-host-top-inset)) * var(--prts-sidebar-image-ratio,1))));
|
|
9
10
|
--prts-wall-x:var(--prts-protected-sidebar-x,calc((var(--prts-sidebar-width,280px) + var(--prts-nav-width) - var(--prts-wall-width)) / 2));
|
|
10
|
-
|
|
11
|
+
background:linear-gradient(var(--prts-background-wash),var(--prts-background-wash)),var(--prts-background-sidebar) var(--prts-wall-x) var(--prts-protected-sidebar-y, 50%) / var(--prts-wall-width) auto no-repeat}
|
|
11
12
|
& [data-prts-region="sessions"]{background-position:center,calc(var(--prts-wall-x) - var(--prts-nav-width)) var(--prts-protected-sidebar-y, 50%)}
|
|
12
13
|
}
|
|
13
14
|
html[data-dsh-prts][data-prts-rail-mode="overlay"]:is([data-prts-background-mode="split"][data-prts-background-sidebar],[data-prts-background-mode="full"][data-prts-background-full]) [data-prts-nav-rail] { background: transparent; }
|
|
@@ -572,11 +572,11 @@ html[data-dsh-prts] [data-prts-session-pickup-bar]:nth-of-type(10) { --prts-pick
|
|
|
572
572
|
html[data-dsh-prts] [data-prts-session-pickup-bar]:nth-of-type(11) { --prts-pickup-duration: 560ms; --prts-pickup-delay: -.3s; }
|
|
573
573
|
|
|
574
574
|
@media (prefers-reduced-motion: no-preference) {
|
|
575
|
-
html[data-dsh-prts]:not([data-prts-motion="reduced"]) [data-prts-session-row]:hover [data-prts-session-pickup-indicator] {
|
|
575
|
+
html[data-dsh-prts]:not([data-prts-motion="reduced"]) [data-prts-session-row]:hover[data-prts-pickup-ready] [data-prts-session-pickup-indicator] {
|
|
576
576
|
animation: prts-session-record-indicator 1.1s ease-in-out infinite;
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
-
html[data-dsh-prts]:not([data-prts-motion="reduced"]) [data-prts-session-row]:hover [data-prts-session-pickup-bar] {
|
|
579
|
+
html[data-dsh-prts]:not([data-prts-motion="reduced"]) [data-prts-session-row]:hover[data-prts-pickup-ready] [data-prts-session-pickup-bar] {
|
|
580
580
|
animation: prts-session-pickup-sample var(--prts-pickup-duration) ease-in-out var(--prts-pickup-delay) infinite;
|
|
581
581
|
}
|
|
582
582
|
}
|
|
@@ -886,3 +886,6 @@ html[data-dsh-prts] [data-prts-tool-record] > div { display:flex;align-items:bas
|
|
|
886
886
|
html[data-dsh-prts] [data-prts-tool-record] b { flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--prts-cyan);font:11px/1.6 var(--prts-mono); }
|
|
887
887
|
html[data-dsh-prts] [data-prts-tool-record] small { flex:none;color:var(--prts-muted);font-size:10px; }
|
|
888
888
|
html[data-dsh-prts] [data-prts-tool-record] p { display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;margin:4px 0 0; }
|
|
889
|
+
|
|
890
|
+
/* Show the baseline immediately; only start the pickup after a deliberate hover. */
|
|
891
|
+
html[data-dsh-prts] [data-prts-session-row]:not([data-prts-pickup-ready]) :is([data-prts-session-pickup-bar],[data-prts-session-pickup-indicator]) { visibility:hidden; }
|
|
@@ -508,7 +508,7 @@ html[data-dsh-prts] [data-turn-trigger] {
|
|
|
508
508
|
}
|
|
509
509
|
|
|
510
510
|
/* Body interactions lock onto four corners; shell controls keep their own treatment. */
|
|
511
|
-
html[data-dsh-prts] :is([data-chat-flow-kind],[data-message-role]) :is([data-prts-conversation-control],[data-presented-file],[data-changed-files],[data-produced-files-row] > button) {
|
|
511
|
+
html[data-dsh-prts] :is([data-chat-flow-kind],[data-message-role],[data-submission-echo],[data-pending-steering]) :is([data-prts-conversation-control],[data-presented-file],[data-changed-files],[data-produced-files-row] > button) {
|
|
512
512
|
position:relative;
|
|
513
513
|
--prts-control-outline:var(--prts-line);
|
|
514
514
|
&:is(:hover,:focus-visible,:focus-within) {
|
|
@@ -967,7 +967,8 @@ html[data-dsh-prts] [data-prts-transfer] {
|
|
|
967
967
|
& [data-prts-transfer-option] { align-items:flex-start; flex-wrap:nowrap; padding:12px 0 0; }
|
|
968
968
|
& [data-prts-transfer-option] input { margin:5px 0 0; }
|
|
969
969
|
& [data-prts-transfer-option] small { display:block; margin-top:4px; }
|
|
970
|
-
& [data-prts-transfer-file-hint] { display:
|
|
970
|
+
& [data-prts-transfer-card] > [data-prts-transfer-file-hint] { display:flex; flex-direction:column; justify-content:center; flex:1; gap:6px; min-height:130px; padding:16px 12px; border:1px dashed var(--prts-line-strong); text-align:center; white-space:normal; }
|
|
971
|
+
& [data-prts-transfer-file-hint] > strong { font-size:13px; font-weight:500; }
|
|
971
972
|
& [data-prts-transfer-file-hint] > span { font-size:24px; color:var(--prts-muted); }
|
|
972
973
|
& [data-prts-transfer-buttons] { display:flex; gap:8px; }
|
|
973
974
|
& [data-prts-transfer-compatibility] { margin:0; padding:10px 12px; border:1px solid var(--prts-yellow); overflow-wrap:anywhere; }
|
|
@@ -1032,3 +1033,15 @@ html[data-dsh-prts] [data-prts-transfer]:not([aria-busy="true"]) [data-prts-tran
|
|
|
1032
1033
|
}
|
|
1033
1034
|
html[data-prts-motion="reduced"] [data-prts-transfer-card] { transition:none; }
|
|
1034
1035
|
@media (prefers-reduced-motion:reduce) { [data-prts-transfer-card] { transition:none; } }
|
|
1036
|
+
|
|
1037
|
+
html[data-dsh-prts] [data-prts-setting-row="cornerColor"] { flex-wrap:wrap;gap:16px; }
|
|
1038
|
+
html[data-dsh-prts] [data-prts-corner-presets] { display:flex;flex-wrap:wrap;gap:6px;align-items:center;
|
|
1039
|
+
& > button { position:relative;display:grid;place-items:center;width:32px;height:32px;padding:5px;border:1px solid transparent;border-radius:4px;background:transparent; }
|
|
1040
|
+
& i { display:block;width:18px;height:18px;border-radius:3px;background:var(--swatch);box-shadow:inset 0 0 0 1px #8884; }
|
|
1041
|
+
& > button:hover { background:var(--prts-panel-raised); }
|
|
1042
|
+
& > button[aria-pressed="true"] { border-color:var(--prts-ink);background:var(--prts-panel-raised); }
|
|
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
|
+
& > button:focus-visible { outline:2px solid var(--prts-ink);outline-offset:2px; }
|
|
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)); }
|
|
@@ -407,14 +407,28 @@ const GlassSurface = ({
|
|
|
407
407
|
useEffect(() => {
|
|
408
408
|
if (!containerRef.current) return;
|
|
409
409
|
|
|
410
|
-
|
|
410
|
+
// PRTS: keep the existing map stretched with the live container while
|
|
411
|
+
// resizing. Rebuilding its distance field on every intermediate width
|
|
412
|
+
// defeats the outer composer's settled-shape update.
|
|
413
|
+
let timer;
|
|
414
|
+
let committed = '';
|
|
415
|
+
const commit = () => {
|
|
416
|
+
timer = undefined;
|
|
411
417
|
const element = containerRef.current;
|
|
412
|
-
if (element
|
|
418
|
+
if (!element || !element.offsetWidth || !element.offsetHeight) return;
|
|
419
|
+
const next = `${element.offsetWidth}x${element.offsetHeight}`;
|
|
420
|
+
if (next !== committed) { committed = next; setBoxSize(next); }
|
|
421
|
+
};
|
|
422
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
423
|
+
clearTimeout(timer);
|
|
424
|
+
if (!committed) commit();
|
|
425
|
+
else timer = setTimeout(commit, 160);
|
|
413
426
|
});
|
|
414
427
|
|
|
415
428
|
resizeObserver.observe(containerRef.current);
|
|
416
429
|
|
|
417
430
|
return () => {
|
|
431
|
+
clearTimeout(timer);
|
|
418
432
|
resizeObserver.disconnect();
|
|
419
433
|
};
|
|
420
434
|
}, []);
|