@ash-qw/dsh-theme-prts 2.5.3 → 2.5.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 +27 -0
- package/lib/client.js +238 -47
- package/package.json +1 -1
- package/src/client/background-layout.js +21 -7
- package/src/client/conversation-control-adapter.js +41 -11
- package/src/client/cursor-adapter.js +41 -9
- 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/preview-markdown.js +5 -2
- package/src/client/session-summary-preview.js +49 -5
- 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 +16 -4
- package/src/styles/prts.css +1 -1
- 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
|
}
|
|
@@ -23,6 +23,8 @@ function isPendingInteractionAction(button) {
|
|
|
23
23
|
function classify(button, operation) {
|
|
24
24
|
// The host's process disclosure owns its transparent, full-width hit area.
|
|
25
25
|
if (button.hasAttribute('data-turn-process')) return null
|
|
26
|
+
// Schedule cards use an absolute transparent button as the whole-card hit area.
|
|
27
|
+
if (button.parentElement?.matches('[data-tool="schedule_create"]') && hasClassSuffix(button, '_cardOpen')) return null
|
|
26
28
|
if (button.querySelector('[data-compaction-disclosure]')) return null
|
|
27
29
|
// Notification headers belong to the card surface, not standalone buttons.
|
|
28
30
|
if (button.parentElement?.matches('[data-turn-trigger]')) return null
|
|
@@ -49,6 +51,8 @@ function classify(button, operation) {
|
|
|
49
51
|
if (approval) return hasClassSuffix(button, '_reject') ? 'danger' : 'cta'
|
|
50
52
|
if (hasClassSuffix(button, '_modalAction') || button.closest('[role="dialog"]')) return 'cta'
|
|
51
53
|
|
|
54
|
+
const userRow = closestWithClassSuffix(button, '_userRow')
|
|
55
|
+
if (userRow?.matches('[data-submission-echo], [data-pending-steering]')) return 'micro'
|
|
52
56
|
if (button.closest('[data-chat-flow-kind], [data-message-role]')) return 'micro'
|
|
53
57
|
return null
|
|
54
58
|
}
|
|
@@ -129,7 +133,7 @@ export function createConversationControlAdapter({ document, window }) {
|
|
|
129
133
|
subtree: true,
|
|
130
134
|
childList: true,
|
|
131
135
|
attributes: true,
|
|
132
|
-
attributeFilter: ['class', 'data-approval-key', 'data-turn-process'],
|
|
136
|
+
attributeFilter: ['class', 'data-approval-key', 'data-turn-process', 'data-submission-echo', 'data-pending-steering', 'data-chat-flow-kind', 'data-message-role'],
|
|
133
137
|
})
|
|
134
138
|
},
|
|
135
139
|
dispose() {
|
|
@@ -166,24 +170,36 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
166
170
|
}
|
|
167
171
|
let target=null, hovered=null, focused=null, raf=0, pressed=false, releasePending=false, disposed=false
|
|
168
172
|
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
|
-
|
|
173
|
+
let moveRaf=0, pendingMove=null
|
|
174
|
+
const resizeObserver=window.ResizeObserver?new window.ResizeObserver(entries=>{
|
|
175
|
+
if(!target)return
|
|
176
|
+
const entry=entries.find(item=>item.target===target),size=entry?.borderBoxSize?.[0]
|
|
177
|
+
if(size && bounds && Math.abs(size.inlineSize-bounds.width)<.5 && Math.abs(size.blockSize-bounds.height)<.5)return
|
|
178
|
+
const box=target.getBoundingClientRect()
|
|
179
|
+
if(bounds && ['left','top','width','height'].every(key=>bounds[key]===box[key]))return
|
|
180
|
+
boundsDirty=true;wake()
|
|
181
|
+
}):null
|
|
170
182
|
const reduced=window.matchMedia?.('(prefers-reduced-motion: reduce)')
|
|
171
183
|
const fine=window.matchMedia?.('(hover:hover) and (pointer:fine)')
|
|
172
184
|
let activeModal=null
|
|
173
185
|
const find=node=>{
|
|
174
186
|
if(activeModal && !activeModal.contains(node))return null
|
|
175
187
|
if (!node?.closest || node.closest('[data-prts-workspace-actions],[data-prts-session-actions]')) return null
|
|
176
|
-
const
|
|
188
|
+
const schedule=node.closest('[data-tool="schedule_create"]')
|
|
189
|
+
const scheduleOpen=schedule && [...schedule.children].find(child=>child.tagName==='BUTTON' && hasClassSuffix(child,'_cardOpen') && !child.matches(':disabled,[aria-disabled="true"]'))
|
|
177
190
|
const button=node.closest('button')
|
|
191
|
+
// The transparent hit layer targets the card; nested actions keep their own frame.
|
|
192
|
+
const card=node.closest('[data-presented-file],[data-changed-files]') || (scheduleOpen && (!button || button===scheduleOpen)?schedule:null)
|
|
178
193
|
const pendingAction=operation.contains(button) && isPendingInteractionAction(button) ? button : null
|
|
179
194
|
const candidate=card || pendingAction || node.closest(selector)
|
|
180
195
|
return candidate && !candidate.matches(':disabled,[aria-disabled="true"]') && !candidate.closest('[inert]') ? candidate : null
|
|
181
196
|
}
|
|
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}
|
|
197
|
+
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
198
|
const wake=()=>{if(!raf && !disposed)raf=window.requestAnimationFrame(draw)}
|
|
184
199
|
const choose=()=>{
|
|
185
200
|
const next=(hovered?.isConnected?hovered:null) || (focused?.isConnected?focused:null)
|
|
186
201
|
if(next!==target){
|
|
202
|
+
const nextBounds=next?.getBoundingClientRect()
|
|
187
203
|
const retire=!next && target && !target.isConnected && targetRetires && releasePending && bounds && !reduced?.matches
|
|
188
204
|
imageRetiring=false
|
|
189
205
|
retiringBounds=retire?{left:bounds.left,top:bounds.top,width:bounds.width,height:bounds.height}:null
|
|
@@ -195,12 +211,13 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
195
211
|
if(target){
|
|
196
212
|
targetRetires=hasClassSuffix(target,'_toBottom') || isPendingInteractionAction(target)
|
|
197
213
|
target.setAttribute('data-prts-corner-target','');showFrame()
|
|
198
|
-
measure();const box=bounds
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
frame.style.color=
|
|
214
|
+
measure(nextBounds);const box=bounds
|
|
215
|
+
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}
|
|
216
|
+
frame.style.opacity='0'
|
|
217
|
+
frame.style.color=target.getAttribute('data-prts-conversation-control')==='danger'?window.getComputedStyle(target).getPropertyValue('--prts-red'):'var(--prts-corner-color, var(--prts-yellow))'
|
|
202
218
|
}
|
|
203
219
|
}
|
|
220
|
+
if(!target && !state?.alpha)hideFrame()
|
|
204
221
|
if(!settled || !target && state?.alpha>0)wake()
|
|
205
222
|
}
|
|
206
223
|
function draw(time) {
|
|
@@ -227,7 +244,7 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
227
244
|
tween={from:{...state},to:{...goal},start:time,duration:remaining,pressed}
|
|
228
245
|
}
|
|
229
246
|
// 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)
|
|
247
|
+
const progress=reduced?.matches || settled || !tween.duration || (!state.alpha && !goal.alpha)?1:Math.min(1,(time-tween.start)/tween.duration)
|
|
231
248
|
const ease=1-(1-progress)**3
|
|
232
249
|
for(const key of keys)state[key]=tween.from[key]+(goal[key]-tween.from[key])*ease
|
|
233
250
|
const moving=progress<1
|
|
@@ -251,17 +268,28 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
251
268
|
else if(moving)wake()
|
|
252
269
|
else {tween=null;if(!state.alpha)hideFrame()}
|
|
253
270
|
}
|
|
254
|
-
const
|
|
271
|
+
const applyMove=event=>{
|
|
255
272
|
if(document.hidden || event.pointerType==='touch' || !fine?.matches)return
|
|
256
273
|
pointer={x:event.clientX,y:event.clientY}
|
|
257
274
|
const next=find(event.target)
|
|
258
275
|
if(next===hovered && (next || !state?.alpha))return
|
|
259
276
|
hovered=next;choose()
|
|
260
277
|
}
|
|
261
|
-
const
|
|
278
|
+
const clearMove=()=>{window.cancelAnimationFrame(moveRaf);moveRaf=0;pendingMove=null}
|
|
279
|
+
const flushMove=()=>{
|
|
280
|
+
const event=pendingMove;clearMove()
|
|
281
|
+
if(event)applyMove(event)
|
|
282
|
+
}
|
|
283
|
+
const move=event=>{
|
|
284
|
+
if(document.hidden || event.pointerType==='touch' || !fine?.matches)return
|
|
285
|
+
pendingMove={target:event.target,clientX:event.clientX,clientY:event.clientY,pointerType:event.pointerType}
|
|
286
|
+
if(!moveRaf)moveRaf=window.requestAnimationFrame(flushMove)
|
|
287
|
+
}
|
|
288
|
+
const leave=()=>{clearMove();hovered=null;choose()}
|
|
262
289
|
const focus=event=>{focused=event.target.matches?.(':focus-visible')?find(event.target):null;choose()}
|
|
263
290
|
const blur=()=>{focused=null;choose()}
|
|
264
291
|
const down=event=>{
|
|
292
|
+
flushMove()
|
|
265
293
|
pointer={x:event.clientX,y:event.clientY}
|
|
266
294
|
if(event.pointerType==='touch')hovered=find(event.target)
|
|
267
295
|
choose()
|
|
@@ -275,6 +303,7 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
275
303
|
}
|
|
276
304
|
const up=event=>{release();if(event.pointerType==='touch')hovered=null;choose()}
|
|
277
305
|
const reset=()=>{
|
|
306
|
+
clearMove()
|
|
278
307
|
target?.removeAttribute('data-prts-corner-target');hideFrame()
|
|
279
308
|
hovered=focused=target=null;state=tween=bounds=exitGoal=retiringBounds=null
|
|
280
309
|
imageRetiring=false
|
|
@@ -317,6 +346,7 @@ function mountBodyTargetFrame(document, window, operation) {
|
|
|
317
346
|
]
|
|
318
347
|
for(const [node,event,handler] of listeners)node.addEventListener(event,handler)
|
|
319
348
|
return ()=>{
|
|
349
|
+
clearMove()
|
|
320
350
|
modalObserver?.disconnect();document.removeEventListener('toggle',modalToggle,true)
|
|
321
351
|
disposed=true;target?.removeAttribute('data-prts-corner-target');hideFrame();window.cancelAnimationFrame(raf);resizeObserver?.disconnect()
|
|
322
352
|
document.removeEventListener('scroll',scroll,true);window.removeEventListener('resize',reset)
|
|
@@ -7,8 +7,10 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
7
7
|
let handPhase
|
|
8
8
|
let gestureAssets = assets
|
|
9
9
|
let textTarget
|
|
10
|
+
let moveFrame, pendingMove
|
|
10
11
|
let lastPoint
|
|
11
|
-
const
|
|
12
|
+
const heroTextSelector = '[data-phase="hero"] [class$="_titleGroup"]'
|
|
13
|
+
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
14
|
const controlSelector = 'a[href], button, summary, input, textarea, select, [contenteditable], [role="button"], [role="toolbar"], [role="tab"]'
|
|
13
15
|
function clearTextHit() {
|
|
14
16
|
textTarget?.removeAttribute('data-prts-text-hit')
|
|
@@ -25,8 +27,8 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
25
27
|
if (event.pointerType !== 'mouse') return
|
|
26
28
|
lastPoint = { clientX: event.clientX, clientY: event.clientY, pointerType: 'mouse' }
|
|
27
29
|
const target = event.target?.nodeType === 1 ? event.target : event.target?.parentElement
|
|
28
|
-
const content = target?.closest?.(contentSelector)
|
|
29
|
-
if (!content
|
|
30
|
+
const content = target?.closest?.(`${contentSelector}, ${heroTextSelector}`)
|
|
31
|
+
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
32
|
const style = window.getComputedStyle(target)
|
|
31
33
|
if (style.userSelect === 'none' || !style.cursor.includes('data:image/png')) return
|
|
32
34
|
// Caret APIs return the nearest insertion point even over empty space.
|
|
@@ -89,6 +91,7 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
89
91
|
let originalPriority
|
|
90
92
|
let appliedCursor
|
|
91
93
|
let appliedPriority
|
|
94
|
+
let lastHandFrame
|
|
92
95
|
function ownsHandCursor() {
|
|
93
96
|
return handTarget?.style.cursor === appliedCursor
|
|
94
97
|
&& handTarget.style.getPropertyPriority('cursor') === appliedPriority
|
|
@@ -105,6 +108,7 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
105
108
|
}
|
|
106
109
|
handTarget?.removeAttribute('data-prts-hand-active')
|
|
107
110
|
handTarget = undefined
|
|
111
|
+
lastHandFrame = undefined
|
|
108
112
|
}
|
|
109
113
|
// Cursor is inherited. Animating a large container still restyles its subtree.
|
|
110
114
|
// Keep those background hits static, while controls retain native animation.
|
|
@@ -129,7 +133,9 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
129
133
|
}
|
|
130
134
|
function applyHandFrame() {
|
|
131
135
|
if (stopIfHandChanged() || !handTarget) return
|
|
136
|
+
if(lastHandFrame === handCursor && ownsHandCursor())return
|
|
132
137
|
handTarget.style.setProperty('cursor', handCursor)
|
|
138
|
+
lastHandFrame = handCursor
|
|
133
139
|
appliedCursor = handTarget.style.cursor
|
|
134
140
|
appliedPriority = handTarget.style.getPropertyPriority('cursor')
|
|
135
141
|
}
|
|
@@ -155,20 +161,27 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
155
161
|
}
|
|
156
162
|
function play(frames, done) {
|
|
157
163
|
clearTimer()
|
|
158
|
-
|
|
164
|
+
const started = window.performance.now()
|
|
165
|
+
let index = 0, deadline = frames[0]?.duration ?? 0
|
|
159
166
|
function next() {
|
|
160
167
|
timer = undefined
|
|
161
168
|
if (stopIfHandChanged()) return
|
|
162
|
-
|
|
169
|
+
const elapsed = window.performance.now() - started
|
|
170
|
+
// Skip expired frames after a busy main thread; never replay a backlog.
|
|
171
|
+
while (index < frames.length - 1 && elapsed >= deadline) {
|
|
172
|
+
index += 1
|
|
173
|
+
deadline += frames[index]?.duration ?? 0
|
|
174
|
+
}
|
|
175
|
+
// The source ANI ends with a 60-second hold frame, which is not animated.
|
|
163
176
|
if (index >= frames.length - 1) { done(); return }
|
|
164
|
-
|
|
165
|
-
show(frame.url)
|
|
177
|
+
show(frames[index].url)
|
|
166
178
|
if (!handCursor) return
|
|
167
|
-
timer = window.setTimeout(next,
|
|
179
|
+
timer = window.setTimeout(next, Math.max(1, deadline - (window.performance.now() - started)))
|
|
168
180
|
}
|
|
169
181
|
next()
|
|
170
182
|
}
|
|
171
183
|
function down(event) {
|
|
184
|
+
clearPendingMove()
|
|
172
185
|
if (event.pointerType !== 'mouse' || event.button !== 0 || event.isPrimary === false) return
|
|
173
186
|
updateResize(event)
|
|
174
187
|
updateTextHit(event)
|
|
@@ -202,13 +215,32 @@ export function createCursorAdapter({ document, window, assets }) {
|
|
|
202
215
|
if (handPhase === 'press') return
|
|
203
216
|
releaseHand()
|
|
204
217
|
}
|
|
205
|
-
function
|
|
218
|
+
function applyMove(event) {
|
|
206
219
|
updateResize(event)
|
|
207
220
|
updateTextHit(event)
|
|
208
221
|
if (handCursor) followHand(event.target?.nodeType === 1 ? event.target : event.target?.parentElement)
|
|
209
222
|
if (pointerId !== undefined && event.pointerId === pointerId && !(event.buttons & 1)) reset()
|
|
210
223
|
}
|
|
224
|
+
function clearPendingMove() {
|
|
225
|
+
if (moveFrame !== undefined) window.cancelAnimationFrame?.(moveFrame)
|
|
226
|
+
moveFrame = undefined
|
|
227
|
+
pendingMove = undefined
|
|
228
|
+
}
|
|
229
|
+
function move(event) {
|
|
230
|
+
if (event.pointerType !== 'mouse') return
|
|
231
|
+
// Lost releases must clear synchronously even when the next paint is delayed.
|
|
232
|
+
if (pointerId !== undefined && event.pointerId === pointerId && !(event.buttons & 1)) reset()
|
|
233
|
+
if (!window.requestAnimationFrame) { applyMove(event); return }
|
|
234
|
+
pendingMove = { target:event.target, pointerType:event.pointerType, pointerId:event.pointerId,
|
|
235
|
+
buttons:event.buttons, clientX:event.clientX, clientY:event.clientY }
|
|
236
|
+
if (moveFrame === undefined) moveFrame = window.requestAnimationFrame(() => {
|
|
237
|
+
const latest = pendingMove
|
|
238
|
+
clearPendingMove()
|
|
239
|
+
if (latest?.target?.isConnected) applyMove(latest)
|
|
240
|
+
})
|
|
241
|
+
}
|
|
211
242
|
function resetInteraction() {
|
|
243
|
+
clearPendingMove()
|
|
212
244
|
reset()
|
|
213
245
|
clearResize()
|
|
214
246
|
clearTextHit()
|
|
@@ -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]
|
|
@@ -27,9 +27,12 @@ export function createPreviewContentCache(document, render = renderPreviewMarkdo
|
|
|
27
27
|
const html = render(text)
|
|
28
28
|
const plain = document.createElement('div')
|
|
29
29
|
plain.innerHTML = html
|
|
30
|
-
const
|
|
30
|
+
const fullSummary = plain.textContent.replace(/\s+/g, ' ').trim()
|
|
31
|
+
const summary = fullSummary.slice(0, 100)
|
|
32
|
+
const truncated = fullSummary.length > summary.length
|
|
33
|
+
const needsDetail = truncated || Boolean(plain.querySelector('a,pre,ul,ol,table,blockquote,h1,h2,h3,h4,h5,h6,br') || plain.children.length > 1)
|
|
31
34
|
const cost = text.length + html.length + summary.length
|
|
32
|
-
const value = {html, summary, cost}
|
|
35
|
+
const value = {html, summary, cost, needsDetail, truncated}
|
|
33
36
|
if (cost <= 262144) {
|
|
34
37
|
while (entries.size && (entries.size >= 32 || size + cost > 262144)) {
|
|
35
38
|
const key = entries.keys().next().value
|
|
@@ -293,12 +293,29 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
293
293
|
const height = Math.max(1, (viewport?.height ?? window.innerHeight) - 16)
|
|
294
294
|
const place = (surface, reference, preferredTop, noticeOwner, anchorBottom = preferredTop) => {
|
|
295
295
|
surface.style.maxWidth = 'none'
|
|
296
|
-
|
|
296
|
+
let fixedEdge
|
|
297
|
+
if (surface === panel) {
|
|
298
|
+
// Pick a direction once, before async replies arrive; keep the same edge
|
|
299
|
+
// while content grows and let the existing scroll surface absorb overflow.
|
|
300
|
+
if (!current.placement) {
|
|
301
|
+
const below = top + height - preferredTop
|
|
302
|
+
const above = anchorBottom - top
|
|
303
|
+
current.placement = below < 160 && above > below
|
|
304
|
+
? { direction: 'above', edge: anchorBottom }
|
|
305
|
+
: { direction: 'below', edge: preferredTop }
|
|
306
|
+
}
|
|
307
|
+
fixedEdge = Math.max(top, Math.min(current.placement.edge, top + height))
|
|
308
|
+
const available = current.placement.direction === 'above' ? fixedEdge - top : top + height - fixedEdge
|
|
309
|
+
surface.style.maxHeight = Math.max(1, available) + 'px'
|
|
310
|
+
surface.setAttribute('data-prts-preview-direction', current.placement.direction)
|
|
311
|
+
} else surface.style.maxHeight = 'none'
|
|
297
312
|
const bounds = surface.getBoundingClientRect()
|
|
298
313
|
const x = reference ? reference.right + 10 : left
|
|
299
|
-
const fitsVertically = y => y >= top && y + bounds.height <= top + height
|
|
314
|
+
const fitsVertically = y => y >= top - 1 && y + bounds.height <= top + height + 1
|
|
300
315
|
const above = anchorBottom - bounds.height
|
|
301
|
-
const y =
|
|
316
|
+
const y = surface === panel
|
|
317
|
+
? current.placement.direction === 'above' ? fixedEdge - bounds.height : fixedEdge
|
|
318
|
+
: !fitsVertically(preferredTop) && fitsVertically(above) ? above : preferredTop
|
|
302
319
|
const insufficient = Boolean(reference) && (x < left || x + bounds.width > left + width || !fitsVertically(y))
|
|
303
320
|
surface.toggleAttribute('data-prts-preview-no-space', insufficient)
|
|
304
321
|
const noticeKey = surface === panel ? 'spaceNotice' : 'detailSpaceNotice'
|
|
@@ -319,6 +336,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
319
336
|
surface.style.top = y + 'px'
|
|
320
337
|
return !insufficient
|
|
321
338
|
}
|
|
339
|
+
updateReplyAvailability()
|
|
322
340
|
const menu = current.anchor?.closest('[data-prts-session-summary-popover]')
|
|
323
341
|
const reference = menu?.getBoundingClientRect()
|
|
324
342
|
const row = current.anchor?.getBoundingClientRect()
|
|
@@ -343,6 +361,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
343
361
|
current.spaceNotice?.remove()
|
|
344
362
|
current.detailSpaceNotice?.remove()
|
|
345
363
|
panel.removeAttribute('data-prts-preview-no-space')
|
|
364
|
+
panel.removeAttribute('data-prts-preview-direction')
|
|
365
|
+
panel.style.removeProperty('max-height')
|
|
346
366
|
positionObserver?.disconnect()
|
|
347
367
|
window.cancelAnimationFrame(positionFrame)
|
|
348
368
|
window.removeEventListener('resize', schedulePosition)
|
|
@@ -537,8 +557,24 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
537
557
|
detailEntry?.removeAttribute('data-prts-reply-active')
|
|
538
558
|
detailEntry = undefined
|
|
539
559
|
}
|
|
560
|
+
const replyIsTruncated = item => {
|
|
561
|
+
const summary = item.querySelector('span')
|
|
562
|
+
return item.dataset.prtsSummaryTruncated === 'true' || summary.scrollHeight > summary.clientHeight + 1 || summary.scrollWidth > summary.clientWidth + 1
|
|
563
|
+
}
|
|
564
|
+
const updateReplyAvailability = () => {
|
|
565
|
+
const entries = [...body.querySelectorAll('[data-prts-reply-entry]')].map(item => [item, replyIsTruncated(item)])
|
|
566
|
+
for (const [item, truncated] of entries) {
|
|
567
|
+
const needed = truncated || item.dataset.prtsReplyTruncated === 'true'
|
|
568
|
+
item.toggleAttribute('data-prts-reply-overflow', truncated)
|
|
569
|
+
item.toggleAttribute('data-prts-reply-complete', !needed)
|
|
570
|
+
item.tabIndex = needed ? 0 : -1
|
|
571
|
+
item.setAttribute('aria-disabled', String(!needed))
|
|
572
|
+
if (!needed && detailEntry === item) hideDetail()
|
|
573
|
+
}
|
|
574
|
+
}
|
|
540
575
|
const showDetail = (reply, index, item, refresh = false) => {
|
|
541
576
|
keep()
|
|
577
|
+
if (!reply.tool && item.hasAttribute('data-prts-reply-complete')) { hideDetail(); return }
|
|
542
578
|
if (!refresh && detailKey === reply.key && current.detail && !current.detail.hidden) return
|
|
543
579
|
if (!current.detail) {
|
|
544
580
|
const detail = panel.ownerDocument.createElement('section')
|
|
@@ -708,8 +744,15 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
708
744
|
icon.append(outline)
|
|
709
745
|
heading.prepend(icon)
|
|
710
746
|
const summary = panel.ownerDocument.createElement('span')
|
|
711
|
-
|
|
712
|
-
|
|
747
|
+
const content = current.contentCache(reply.text)
|
|
748
|
+
summary.textContent = content.summary
|
|
749
|
+
item.dataset.prtsReplyTruncated = String(content.needsDetail)
|
|
750
|
+
item.dataset.prtsSummaryTruncated = String(content.truncated)
|
|
751
|
+
const ellipsis = panel.ownerDocument.createElement('i')
|
|
752
|
+
ellipsis.setAttribute('data-prts-reply-ellipsis', '')
|
|
753
|
+
ellipsis.setAttribute('aria-hidden', 'true')
|
|
754
|
+
ellipsis.textContent = '…'
|
|
755
|
+
item.append(heading, summary, ellipsis)
|
|
713
756
|
item.addEventListener('pointermove', () => showDetail(reply, index, item))
|
|
714
757
|
item.addEventListener('focus', () => showDetail(reply, index, item))
|
|
715
758
|
item.addEventListener('click', () => showDetail(reply, index, item))
|
|
@@ -725,6 +768,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
725
768
|
if (scrollAnchor?.isConnected) body.scrollTop += scrollAnchor.getBoundingClientRect().top - anchorTop
|
|
726
769
|
else if (previousTop === 0) body.scrollTop = 0
|
|
727
770
|
historyKeys = replies
|
|
771
|
+
schedulePosition()
|
|
728
772
|
previousText = undefined
|
|
729
773
|
previousQuestion = undefined
|
|
730
774
|
return
|
|
@@ -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',
|