@ash-qw/dsh-theme-prts 1.0.6 → 1.1.1
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 +15 -0
- package/README.en.md +9 -1
- package/README.md +8 -2
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/THIRD_PARTY_NOTICES.zh-CN.md +4 -2
- package/lib/client.js +656 -130
- package/lib/index.js +1 -3
- package/package.json +5 -2
- package/src/assets/cursors/animation.json +56 -0
- package/src/assets/cursors/prts-grab.png +0 -0
- package/src/assets/cursors/prts-grabbing.png +0 -0
- package/src/assets/cursors/prts-not-allowed.png +0 -0
- package/src/client/cursor-adapter.js +2 -2
- package/src/client/custom-emblem.js +404 -0
- package/src/client/emblem-reorder.js +143 -0
- package/src/client/emblem-tool-preview.js +17 -0
- package/src/client/index.js +7 -1
- package/src/client/operations-shell-v2.js +2 -8
- package/src/client/particle-field-adapter.js +32 -7
- package/src/client/theme-settings-workbench.js +64 -48
- package/src/index.js +13 -1
- package/src/server/api.js +54 -0
- package/src/server/emblems.js +153 -0
- package/src/server/image-worker.js +32 -0
- package/src/server/tools.js +67 -0
- package/src/styles/cursor.css +7 -1
- package/src/styles/prts.css +0 -8
- package/src/styles/refinement.css +2 -2
- package/src/styles/theme-settings-workbench.css +82 -85
- package/src/client/resize-shield-adapter.js +0 -69
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, rename, readdir, unlink } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { randomUUID, createHash } from 'node:crypto'
|
|
4
|
+
import { Worker } from 'node:worker_threads'
|
|
5
|
+
import { defaultEmblemConfiguration, validEmblemConfiguration } from '../client/custom-emblem.js'
|
|
6
|
+
|
|
7
|
+
export const EMBLEMS = [
|
|
8
|
+
['rhodes-island','罗德岛'],['lungmen','龙门'],['penguin-logistics','企鹅物流'],['rhine-lab','莱茵生命'],['reunion','整合运动'],
|
|
9
|
+
].map(([key,label])=>({key,label}))
|
|
10
|
+
export const API = '/prts-theme-api/emblems'
|
|
11
|
+
export const DEFAULT_PARAMS = Object.freeze({mode:'auto',threshold:128,zoom:.85,x:0,y:0})
|
|
12
|
+
export function fault(status,message) { return Object.assign(new Error(message),{status}) }
|
|
13
|
+
export function parameters(input = {}, base = DEFAULT_PARAMS) {
|
|
14
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) throw fault(400,'编辑参数无效')
|
|
15
|
+
const result = {...base,...input}
|
|
16
|
+
if (Object.keys(result).some(k=>!Object.hasOwn(DEFAULT_PARAMS,k)) || !['auto','alpha','dark','light'].includes(result.mode)
|
|
17
|
+
|| !Number.isInteger(result.threshold) || result.threshold<1 || result.threshold>254
|
|
18
|
+
|| !Number.isFinite(result.zoom) || result.zoom<.5 || result.zoom>2
|
|
19
|
+
|| !Number.isFinite(result.x) || Math.abs(result.x)>.5 || !Number.isFinite(result.y) || Math.abs(result.y)>.5) throw fault(400,'编辑参数超出范围')
|
|
20
|
+
return result
|
|
21
|
+
}
|
|
22
|
+
export function processImage(bytes, params, signal) {
|
|
23
|
+
signal?.throwIfAborted()
|
|
24
|
+
return new Promise((resolve,reject)=>{
|
|
25
|
+
const worker = new Worker(new URL('./image-worker.js',import.meta.url), {workerData:{bytes,params},execArgv:[]})
|
|
26
|
+
let settled = false
|
|
27
|
+
const finish = async (error,value) => {
|
|
28
|
+
if(settled)return; settled=true;clearTimeout(timer);signal?.removeEventListener('abort',abort)
|
|
29
|
+
await worker.terminate()
|
|
30
|
+
if(error)reject(error);else resolve(value)
|
|
31
|
+
}
|
|
32
|
+
const abort = ()=>void finish(signal.reason || new Error('已取消'))
|
|
33
|
+
const timer = setTimeout(()=>void finish(fault(408,'图片处理超时,请缩小图片后重试')),15000)
|
|
34
|
+
signal?.addEventListener('abort',abort,{once:true})
|
|
35
|
+
worker.once('error',error=>void finish(error))
|
|
36
|
+
worker.once('exit',code=>{if(!settled)void finish(new Error(`图片处理进程提前退出 (${code})`))})
|
|
37
|
+
worker.once('message',result=>void finish(result.error?fault(400,result.error):null,result))
|
|
38
|
+
if(signal?.aborted)abort()
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
async function atomic(path,value) {
|
|
42
|
+
const temp = `${path}.${randomUUID()}.tmp`
|
|
43
|
+
await writeFile(temp,value,{mode:0o600});await rename(temp,path)
|
|
44
|
+
}
|
|
45
|
+
const idPattern = /^[a-zA-Z0-9-]{1,80}$/
|
|
46
|
+
export function checkedId(id) { if(typeof id!=='string'||!idPattern.test(id))throw fault(400,'徽记标识无效');return id }
|
|
47
|
+
|
|
48
|
+
export async function createEmblemService(directory) {
|
|
49
|
+
await mkdir(directory,{recursive:true,mode:0o700})
|
|
50
|
+
let state = {revision:0,configuration:defaultEmblemConfiguration(EMBLEMS)}
|
|
51
|
+
try {
|
|
52
|
+
const stored=JSON.parse(await readFile(join(directory,'configuration.json'),'utf8'))
|
|
53
|
+
if(!Number.isInteger(stored.revision)||!validEmblemConfiguration(stored.configuration,EMBLEMS))throw Error('已保存的徽记配置无效')
|
|
54
|
+
state=stored
|
|
55
|
+
} catch(error) { if(error.code!=='ENOENT')throw error }
|
|
56
|
+
let queue=Promise.resolve(), processing=false
|
|
57
|
+
const listeners=new Set()
|
|
58
|
+
const serialize=fn=>{const next=queue.then(fn);queue=next.catch(()=>{});return next}
|
|
59
|
+
const snapshot=()=>structuredClone(state)
|
|
60
|
+
const draft=async id=>{
|
|
61
|
+
try{return JSON.parse(await readFile(join(directory,`${checkedId(id)}.draft.json`),'utf8'))}
|
|
62
|
+
catch(error){if(error.code==='ENOENT')throw fault(404,'草稿不存在');throw error}
|
|
63
|
+
}
|
|
64
|
+
const save = (configuration,revision,signal)=>serialize(async()=>{
|
|
65
|
+
signal?.throwIfAborted()
|
|
66
|
+
if(revision!==state.revision)throw fault(409,'徽记设置已在其他会话或页面更新,请重新读取后再操作')
|
|
67
|
+
if(configuration?.version!==2 || !Array.isArray(configuration.conversation) || configuration.conversation.length<1 || configuration.conversation.length>12)throw fault(400,'徽记配置无效')
|
|
68
|
+
// Custom entries must be generated by this service; clients cannot inject arbitrary particle arrays.
|
|
69
|
+
const canonical=structuredClone({version:2,hero:configuration.hero,conversation:configuration.conversation})
|
|
70
|
+
const resolveEntry=async entry=>{
|
|
71
|
+
if(!entry || typeof entry!=='object')throw fault(400,'徽记配置无效')
|
|
72
|
+
if(entry.kind==='builtin')return {kind:'builtin',...EMBLEMS.find(e=>e.key===entry.key)}
|
|
73
|
+
if(entry.kind!=='custom')throw fault(400,'徽记类型无效')
|
|
74
|
+
const source=await draft(entry.draftId)
|
|
75
|
+
return {...source.value,kind:'custom',key:entry.key,label:entry.label,draftId:source.id}
|
|
76
|
+
}
|
|
77
|
+
if(canonical.hero)canonical.hero=await resolveEntry(canonical.hero)
|
|
78
|
+
canonical.conversation=await Promise.all(canonical.conversation.map(resolveEntry))
|
|
79
|
+
if(!validEmblemConfiguration(canonical,EMBLEMS))throw fault(400,'徽记配置无效')
|
|
80
|
+
const next={revision:state.revision+1,configuration:canonical}
|
|
81
|
+
signal?.throwIfAborted();await atomic(join(directory,'configuration.json'),JSON.stringify(next));state=next
|
|
82
|
+
for(const fn of listeners) {try{fn(state.revision)}catch{}}
|
|
83
|
+
return snapshot()
|
|
84
|
+
})
|
|
85
|
+
return {
|
|
86
|
+
snapshot,save,draft,
|
|
87
|
+
async exportAsset(id) {
|
|
88
|
+
const d=await draft(id)
|
|
89
|
+
const bytes=await readFile(join(directory,`${d.sourceId}.source`))
|
|
90
|
+
return {format:'prts-emblem',version:1,label:d.label,params:d.params,image:bytes.toString('base64')}
|
|
91
|
+
},
|
|
92
|
+
async importAsset(asset,signal) {
|
|
93
|
+
if(asset?.format!=='prts-emblem'||asset.version!==1)throw fault(400,'不支持的徽记素材包格式或版本')
|
|
94
|
+
if(typeof asset.label!=='string'||!asset.label.trim()||asset.label.length>100)throw fault(400,'徽记名称无效')
|
|
95
|
+
if(typeof asset.image!=='string'||!asset.image.length||asset.image.length>Math.ceil(8*1024*1024/3)*4
|
|
96
|
+
|| !/^[A-Za-z0-9+/]+={0,2}$/.test(asset.image)||asset.image.length%4!==0)throw fault(400,'素材包图片编码或大小无效')
|
|
97
|
+
const bytes=Buffer.from(asset.image,'base64')
|
|
98
|
+
if(bytes.toString('base64')!==asset.image)throw fault(400,'素材包图片编码无效')
|
|
99
|
+
// Rebuild from the editable source; never trust particle arrays or filesystem paths in a package.
|
|
100
|
+
return this.preview({bytes,params:parameters(asset.params),label:asset.label},signal)
|
|
101
|
+
},
|
|
102
|
+
previewPath: id=>join(directory,`${checkedId(id)}.png`),
|
|
103
|
+
subscribe(fn){listeners.add(fn);return()=>listeners.delete(fn)},
|
|
104
|
+
async preview({bytes,params={},baseId,label},signal) {
|
|
105
|
+
if(processing)throw fault(409,'正在处理另一张徽记,请稍后重试')
|
|
106
|
+
processing=true
|
|
107
|
+
try {
|
|
108
|
+
const previous=baseId?await draft(baseId):null
|
|
109
|
+
const effective=parameters(params,previous?.params)
|
|
110
|
+
if(previous)bytes=await readFile(join(directory,`${previous.sourceId}.source`))
|
|
111
|
+
if(!bytes?.length || bytes.length>8*1024*1024)throw fault(400,'图片须为 8 MB 以内的 PNG、JPEG 或 WebP')
|
|
112
|
+
const drafts=(await readdir(directory)).filter(f=>f.endsWith('.draft.json'))
|
|
113
|
+
if(drafts.length>=200)await serialize(async()=>{
|
|
114
|
+
const protectedIds=new Set([previous?.id,state.configuration.hero?.draftId,...state.configuration.conversation.map(e=>e.draftId)])
|
|
115
|
+
const records=await Promise.all(drafts.map(file=>draft(file.slice(0,-11))))
|
|
116
|
+
const expired=records.filter(d=>!protectedIds.has(d.id)).sort((a,b)=>a.createdAt-b.createdAt).slice(0,drafts.length-199)
|
|
117
|
+
const expiredIds=new Set(expired.map(d=>d.id))
|
|
118
|
+
const retainedSources=new Set(records.filter(d=>!expiredIds.has(d.id)).map(d=>d.sourceId))
|
|
119
|
+
for(const d of expired){await unlink(join(directory,`${d.id}.draft.json`));await unlink(join(directory,`${d.id}.png`))}
|
|
120
|
+
for(const sourceId of new Set(expired.map(d=>d.sourceId)))if(!retainedSources.has(sourceId))await unlink(join(directory,`${sourceId}.source`))
|
|
121
|
+
})
|
|
122
|
+
const result=await processImage(bytes,effective,signal)
|
|
123
|
+
signal?.throwIfAborted()
|
|
124
|
+
const id=randomUUID(),sourceId=createHash('sha256').update(bytes).digest('hex')
|
|
125
|
+
const value={id,sourceId,params:effective,label:String(label||previous?.label||'自定义徽记').slice(0,100),createdAt:Date.now(),value:result.value}
|
|
126
|
+
await atomic(join(directory,`${sourceId}.source`),bytes)
|
|
127
|
+
await atomic(join(directory,`${id}.png`),result.preview)
|
|
128
|
+
await atomic(join(directory,`${id}.draft.json`),JSON.stringify(value))
|
|
129
|
+
return value
|
|
130
|
+
} finally {processing=false}
|
|
131
|
+
},
|
|
132
|
+
async image(id,source=false) {const d=await draft(id);return readFile(join(directory,source?`${d.sourceId}.source`:`${d.id}.png`))},
|
|
133
|
+
async apply({draftId,target,entryKey,revision},signal) {
|
|
134
|
+
const d=await draft(draftId),configuration=snapshot().configuration
|
|
135
|
+
const entry={...d.value,kind:'custom',key:randomUUID(),label:d.label,draftId:d.id}
|
|
136
|
+
if(target==='hero')configuration.hero=entry
|
|
137
|
+
else if(target==='add')configuration.conversation.push(entry)
|
|
138
|
+
else if(target==='replace') {
|
|
139
|
+
const index=configuration.conversation.findIndex(e=>e.key===entryKey)
|
|
140
|
+
if(index<0)throw fault(404,'要替换的正文徽记不存在')
|
|
141
|
+
configuration.conversation[index]=entry
|
|
142
|
+
} else throw fault(400,'应用位置无效')
|
|
143
|
+
return save(configuration,revision,signal)
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function draftView(d) {
|
|
148
|
+
return {draftId:d.id,label:d.label,params:d.params,particles:Object.fromEntries(Object.entries(d.value.targets).map(([k,v])=>[k,v.length])),previewUrl:`${API}/draft/${d.id}/preview.png`}
|
|
149
|
+
}
|
|
150
|
+
export function stateView(state) {
|
|
151
|
+
const entry=e=>e?{key:e.key,label:e.label,kind:e.kind,...(e.draftId?{draftId:e.draftId}:{})}:null
|
|
152
|
+
return {revision:state.revision,scope:'当前 DSH 实例,跨工作区和会话共享',hero:entry(state.configuration.hero),conversation:state.configuration.conversation.map(entry)}
|
|
153
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads'
|
|
2
|
+
import sharp from 'sharp'
|
|
3
|
+
import { processCustomEmblemPixels } from '../client/custom-emblem.js'
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const { bytes, params } = workerData
|
|
7
|
+
const image = sharp(Buffer.from(bytes), { limitInputPixels: 16000000, failOn: 'error' }).rotate()
|
|
8
|
+
const metadata = await image.metadata()
|
|
9
|
+
if (!['png','jpeg','webp'].includes(metadata.format)) throw new Error('仅支持 PNG、JPEG、WebP 图片')
|
|
10
|
+
const { data, info } = await image.ensureAlpha().raw().toBuffer({ resolveWithObject: true })
|
|
11
|
+
let mode = params.mode
|
|
12
|
+
if (mode === 'auto') {
|
|
13
|
+
mode = 'dark'
|
|
14
|
+
for (let i = 3; i < data.length; i += 4) if (data[i] < 128) { mode = 'alpha'; break }
|
|
15
|
+
}
|
|
16
|
+
const scale = 320 / Math.max(info.width, info.height) * params.zoom
|
|
17
|
+
const width = Math.max(1, Math.round(info.width * scale)), height = Math.max(1, Math.round(info.height * scale))
|
|
18
|
+
const resized = await sharp(data, { raw: info }).resize(width, height).raw().toBuffer()
|
|
19
|
+
const rgba = new Uint8ClampedArray(320 * 320 * 4)
|
|
20
|
+
const left = Math.round((320-width)/2 + params.x*320), top = Math.round((320-height)/2 + params.y*320)
|
|
21
|
+
for (let y = Math.max(0,top); y < Math.min(320,top+height); y++) {
|
|
22
|
+
const x = Math.max(0,left), end = Math.min(320,left+width)
|
|
23
|
+
if (end > x) rgba.set(resized.subarray(((y-top)*width+x-left)*4,((y-top)*width+end-left)*4),(y*320+x)*4)
|
|
24
|
+
}
|
|
25
|
+
const value = processCustomEmblemPixels(rgba,320,mode,params.threshold)
|
|
26
|
+
const dots = value.targets.standard.map(p => {
|
|
27
|
+
const r = p.kind === 'skeleton' ? 1.2 : .9
|
|
28
|
+
return `<circle cx="${192+p.x*350}" cy="${192+p.y*350}" r="${r}"/>`
|
|
29
|
+
}).join('')
|
|
30
|
+
const preview = await sharp(Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="384" height="384"><rect width="384" height="384" fill="#0d1114"/><g fill="#edf1f2">${dots}</g></svg>`)).png().toBuffer()
|
|
31
|
+
parentPort.postMessage({ value, preview })
|
|
32
|
+
} catch (error) { parentPort.postMessage({ error: error.message }) }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { draftView, stateView, fault } from './emblems.js'
|
|
2
|
+
|
|
3
|
+
export function sessionImages(agent) {
|
|
4
|
+
if(!agent?.session)throw fault(400,'请在 DSH 会话中调用此工具')
|
|
5
|
+
const images=new Map()
|
|
6
|
+
for(const event of agent.session.snapshotEvents()) {
|
|
7
|
+
if(event.type!=='user/message')continue
|
|
8
|
+
for(const part of event.data.content ?? [])if(part.attachment && (part.type==='image' || part.type==='file' && /\.(png|jpe?g|webp)$/i.test(part.attachment.name || '')))images.set(part.attachment.attachmentId,{...part.attachment,attachmentKind:part.type})
|
|
9
|
+
}
|
|
10
|
+
return [...images.values()]
|
|
11
|
+
}
|
|
12
|
+
export function emblemTools(service,attachments) {
|
|
13
|
+
const output={schema:{type:'object'},render:(_args,value)=>[{type:'text',text:JSON.stringify(value)},...(value.previewImage&&value.imageForModel?[{type:'image',attachment:value.previewImage}]:[])]}
|
|
14
|
+
return [{
|
|
15
|
+
name:'prts_emblem_inspect',
|
|
16
|
+
description:'查看 PRTS 主题的新会话徽记、正文轮换列表、配置版本,以及当前会话已上传的图片。仅当用户要求查看或调整主题徽记时使用。徽记设置跨工作区和会话共享;工具不会修改设置。',
|
|
17
|
+
parameters:{type:'object',properties:{},additionalProperties:false},output,isConcurrencySafe:()=>true,
|
|
18
|
+
async execute(_args,exec){return JSON.parse(JSON.stringify({...stateView(service.snapshot()),images:sessionImages(exec.agent)}))}
|
|
19
|
+
},{
|
|
20
|
+
name:'prts_emblem_preview',
|
|
21
|
+
description:'将当前会话上传的图片制作成 PRTS 粒子徽记草稿,或继续调整已有草稿。传 attachmentId(来自 inspect)或 draftId,二选一。调整已有正式徽记可使用 inspect 返回的 draftId。仅生成预览,不应用。优先原样使用返回的 previewMarkdown 展示给用户;没有此字段时,告知用户可展开工具卡片查看。不要将 previewUrl 当成本地图片路径。标准密度预览不是原图。用户满意后使用 apply。不要声称已看到预览,除非实际通过视觉工具查看。',
|
|
22
|
+
parameters:{type:'object',additionalProperties:false,properties:{
|
|
23
|
+
attachmentId:{type:'string'},draftId:{type:'string'},label:{type:'string',maxLength:100},
|
|
24
|
+
params:{type:'object',additionalProperties:false,properties:{mode:{type:'string',enum:['auto','alpha','dark','light']},threshold:{type:'integer',minimum:1,maximum:254},zoom:{type:'number',minimum:.5,maximum:2},x:{type:'number',minimum:-.5,maximum:.5},y:{type:'number',minimum:-.5,maximum:.5}}},
|
|
25
|
+
}},output,isConcurrencySafe:()=>false,
|
|
26
|
+
async execute(args,exec){
|
|
27
|
+
const images=sessionImages(exec.agent)
|
|
28
|
+
if(Boolean(args.attachmentId)===Boolean(args.draftId))throw fault(400,'请选择一张会话图片或一个已有草稿')
|
|
29
|
+
let bytes,label=args.label
|
|
30
|
+
if(args.attachmentId) {
|
|
31
|
+
const ref=images.find(e=>e.attachmentId===args.attachmentId)
|
|
32
|
+
if(!ref)throw fault(404,'当前会话没有这张图片,请先上传图片或重新读取图片列表')
|
|
33
|
+
if(ref.bytes>8*1024*1024)throw fault(400,'图片超过 8 MB')
|
|
34
|
+
const {attachmentKind,...attachment}=ref
|
|
35
|
+
if(attachmentKind==='file') {
|
|
36
|
+
const chunks=[];let total=0
|
|
37
|
+
for await(const chunk of attachments.readFileStream(attachment,exec.signal)) {
|
|
38
|
+
total+=chunk.length;if(total>8*1024*1024)throw fault(400,'图片超过 8 MB');chunks.push(chunk)
|
|
39
|
+
}
|
|
40
|
+
bytes=Buffer.concat(chunks)
|
|
41
|
+
} else bytes=(await attachments.readImage(attachment,exec.signal)).data
|
|
42
|
+
label ||= ref.name
|
|
43
|
+
}
|
|
44
|
+
const d=await service.preview({bytes,baseId:args.draftId,params:args.params,label},exec.signal)
|
|
45
|
+
const hostPath=service.previewPath(d.id)
|
|
46
|
+
const fs=exec.agent.ctx?.get?.('fs')
|
|
47
|
+
const previewPath=fs?.processPathFromHostPath(hostPath)
|
|
48
|
+
let previewImage, imageForModel=false
|
|
49
|
+
const llm=exec.agent.ctx?.get?.('llm')
|
|
50
|
+
const routed=exec.agent.session.requestHeader?.()?.config
|
|
51
|
+
const provider=routed?.provider ?? exec.agent.options?.provider, model=routed?.model ?? exec.agent.options?.model
|
|
52
|
+
if(llm && provider && model) {
|
|
53
|
+
const info=await llm.resolveModelInfo(provider,model,exec.signal)
|
|
54
|
+
imageForModel=info.inputModalities?.includes('image') === true
|
|
55
|
+
}
|
|
56
|
+
if(imageForModel)previewImage=await attachments.saveImage({data:await service.image(d.id),mediaType:'image/png',name:`prts-preview-${d.id}.png`})
|
|
57
|
+
return JSON.parse(JSON.stringify({...draftView(d),applied:false,revision:service.snapshot().revision,
|
|
58
|
+
...(previewPath?{previewPath,previewMarkdown:``}:{}),
|
|
59
|
+
imageForModel,...(previewImage?{previewImage}:{})}))
|
|
60
|
+
},
|
|
61
|
+
},{
|
|
62
|
+
name:'prts_emblem_apply',
|
|
63
|
+
description:'按用户要求应用 PRTS 粒子徽记草稿。hero 替换新会话徽记;add 添加正文轮换徽记;replace 替换 entryKey 对应的正文徽记。先 inspect 取得 revision;版本冲突时重新读取并核对,不能盲目重试覆盖。只有用户明确要求应用时才调用,讨论或预览不等于应用。',
|
|
64
|
+
parameters:{type:'object',additionalProperties:false,properties:{draftId:{type:'string'},target:{type:'string',enum:['hero','add','replace']},entryKey:{type:'string'},revision:{type:'integer',minimum:0}},required:['draftId','target','revision']},output,isConcurrencySafe:()=>false,
|
|
65
|
+
async execute(args,exec){sessionImages(exec.agent);return {applied:true,...stateView(await service.apply(args,exec.signal))}},
|
|
66
|
+
}]
|
|
67
|
+
}
|
package/src/styles/cursor.css
CHANGED
|
@@ -15,7 +15,13 @@
|
|
|
15
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
16
|
cursor: var(--prts-text-cursor), text;
|
|
17
17
|
}
|
|
18
|
+
html[data-dsh-prts][data-prts-cursor="prts"] :is(.dsh-partner-pendant-hit, .dsh-partner-pendant-anchor, [data-prts-emblem-reorder]):enabled {
|
|
19
|
+
cursor: url("__PRTS_GRAB__") 22 19, grab;
|
|
20
|
+
}
|
|
21
|
+
html[data-dsh-prts][data-prts-cursor="prts"] :is(.dsh-partner-pendant-hit, .dsh-partner-pendant-anchor, [data-prts-emblem-reorder]):enabled:active {
|
|
22
|
+
cursor: url("__PRTS_GRABBING__") 22 19, grabbing;
|
|
23
|
+
}
|
|
18
24
|
html[data-dsh-prts][data-prts-cursor="prts"] :is(:disabled, [aria-disabled="true"]) {
|
|
19
|
-
cursor: not-allowed !important;
|
|
25
|
+
cursor: url("__PRTS_NOT_ALLOWED__") 7 7, not-allowed !important;
|
|
20
26
|
}
|
|
21
27
|
}
|
package/src/styles/prts.css
CHANGED
|
@@ -1461,14 +1461,6 @@ html[data-dsh-prts] [data-prts-particle-layer] {
|
|
|
1461
1461
|
transition: opacity 120ms ease-out;
|
|
1462
1462
|
}
|
|
1463
1463
|
|
|
1464
|
-
/* Keep the host frame measurable while pruning the expensive operation subtree
|
|
1465
|
-
during a viewport resize burst. Navigation and the native sessions sidebar
|
|
1466
|
-
remain rendered; the shield is removed after the viewport settles. */
|
|
1467
|
-
html[data-dsh-prts] [data-prts-resize-shield] {
|
|
1468
|
-
content-visibility: hidden !important;
|
|
1469
|
-
contain-intrinsic-size: auto 800px;
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
1464
|
/* Resize state lives on the small visual surfaces that need it, never on the root tree. */
|
|
1473
1465
|
html[data-dsh-prts] [data-prts-particle-layer][data-prts-resizing] {
|
|
1474
1466
|
opacity: .78;
|
|
@@ -203,8 +203,8 @@ html[data-dsh-prts] [data-prts-composer-fallback]::after {
|
|
|
203
203
|
calc(100% - 2px) 0, 100% 2px, 100% calc(100% - 2px), calc(100% - 2px) 100%,
|
|
204
204
|
2px 100%, 0 calc(100% - 2px), 0 2px
|
|
205
205
|
);
|
|
206
|
-
backdrop-filter:
|
|
207
|
-
-webkit-backdrop-filter:
|
|
206
|
+
backdrop-filter: none;
|
|
207
|
+
-webkit-backdrop-filter: none;
|
|
208
208
|
pointer-events: none;
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -187,7 +187,7 @@ html[data-dsh-prts] [data-prts-settings-common] > header {
|
|
|
187
187
|
|
|
188
188
|
html[data-dsh-prts] [data-prts-settings-presets] > header span,
|
|
189
189
|
html[data-dsh-prts] [data-prts-settings-common] > header span,
|
|
190
|
-
html[data-dsh-prts] [data-prts-settings-advanced]
|
|
190
|
+
html[data-dsh-prts] [data-prts-settings-advanced] > [data-prts-section-heading] > span {
|
|
191
191
|
display: flex;
|
|
192
192
|
align-items: baseline;
|
|
193
193
|
gap: 9px;
|
|
@@ -195,7 +195,7 @@ html[data-dsh-prts] [data-prts-settings-advanced] summary > span {
|
|
|
195
195
|
|
|
196
196
|
html[data-dsh-prts] [data-prts-settings-presets] > header strong,
|
|
197
197
|
html[data-dsh-prts] [data-prts-settings-common] > header strong,
|
|
198
|
-
html[data-dsh-prts] [data-prts-settings-advanced]
|
|
198
|
+
html[data-dsh-prts] [data-prts-settings-advanced] > [data-prts-section-heading] strong {
|
|
199
199
|
font: 800 13px/1 var(--prts-display);
|
|
200
200
|
letter-spacing: .05em;
|
|
201
201
|
}
|
|
@@ -459,31 +459,16 @@ html[data-dsh-prts] [data-prts-theme-settings] input:focus-visible {
|
|
|
459
459
|
outline-offset: 2px;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
html[data-dsh-prts] [data-prts-settings-advanced]
|
|
462
|
+
html[data-dsh-prts] [data-prts-settings-advanced] > [data-prts-section-heading] {
|
|
463
463
|
min-height: 52px;
|
|
464
464
|
display: flex;
|
|
465
465
|
align-items: center;
|
|
466
466
|
justify-content: space-between;
|
|
467
467
|
gap: 12px;
|
|
468
468
|
padding: 11px;
|
|
469
|
-
cursor: pointer;
|
|
470
469
|
list-style: none;
|
|
471
470
|
}
|
|
472
471
|
|
|
473
|
-
html[data-dsh-prts] [data-prts-settings-advanced] summary::-webkit-details-marker { display: none; }
|
|
474
|
-
html[data-dsh-prts] [data-prts-settings-advanced] summary::after {
|
|
475
|
-
content: "+";
|
|
476
|
-
width: 24px;
|
|
477
|
-
height: 24px;
|
|
478
|
-
display: grid;
|
|
479
|
-
place-items: center;
|
|
480
|
-
margin-left: 2px;
|
|
481
|
-
border: 1px solid var(--prts-line);
|
|
482
|
-
color: var(--prts-cyan);
|
|
483
|
-
font: 800 13px/1 var(--prts-mono);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
html[data-dsh-prts] [data-prts-settings-advanced][open] summary::after { content: "−"; }
|
|
487
472
|
html[data-dsh-prts] [data-prts-settings-advanced-content] {
|
|
488
473
|
display: grid;
|
|
489
474
|
grid-template-columns: minmax(0, 1fr);
|
|
@@ -559,39 +544,6 @@ html[data-dsh-prts] [data-prts-setting-range-row] input[type="range"] {
|
|
|
559
544
|
flex: 1;
|
|
560
545
|
}
|
|
561
546
|
html[data-dsh-prts] [data-prts-setting-range] { accent-color: var(--prts-cyan); }
|
|
562
|
-
html[data-dsh-prts] [data-prts-settings-actions] {
|
|
563
|
-
min-height: 58px;
|
|
564
|
-
display: block;
|
|
565
|
-
padding: 0;
|
|
566
|
-
border-bottom: 1px solid var(--prts-line-strong);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
html[data-dsh-prts] [data-prts-persistence-error],
|
|
570
|
-
html[data-dsh-prts] [data-prts-reset-zone] {
|
|
571
|
-
min-height: 58px;
|
|
572
|
-
display: flex;
|
|
573
|
-
align-items: center;
|
|
574
|
-
justify-content: space-between;
|
|
575
|
-
gap: 12px;
|
|
576
|
-
padding: 9px 11px;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
html[data-dsh-prts] [data-prts-persistence-error] { border-bottom: 1px solid var(--prts-yellow); background: color-mix(in srgb, var(--prts-yellow) 7%, transparent); }
|
|
580
|
-
html[data-dsh-prts] [data-prts-persistence-error] > span,
|
|
581
|
-
html[data-dsh-prts] [data-prts-reset-zone] > span { display: grid; gap: 4px; }
|
|
582
|
-
html[data-dsh-prts] [data-prts-persistence-error] strong { color: var(--prts-yellow); }
|
|
583
|
-
html[data-dsh-prts] [data-prts-reset-visual],
|
|
584
|
-
html[data-dsh-prts] [data-prts-reset-confirm-action] { color: var(--prts-yellow); box-shadow: inset 0 0 0 1px var(--prts-yellow); }
|
|
585
|
-
|
|
586
|
-
html[data-dsh-prts] [data-prts-reset-confirm] {
|
|
587
|
-
display: flex;
|
|
588
|
-
align-items: center;
|
|
589
|
-
justify-content: flex-end;
|
|
590
|
-
gap: 5px;
|
|
591
|
-
color: var(--prts-yellow);
|
|
592
|
-
font: 700 8px/1.3 var(--prts-mono);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
547
|
html[data-dsh-prts] [hidden] { display: none !important; }
|
|
596
548
|
|
|
597
549
|
/* Readable settings typography: information never drops below 12px. */
|
|
@@ -615,7 +567,7 @@ html[data-dsh-prts] :is(
|
|
|
615
567
|
html[data-dsh-prts] :is(
|
|
616
568
|
[data-prts-settings-presets] > header strong,
|
|
617
569
|
[data-prts-settings-common] > header strong,
|
|
618
|
-
[data-prts-settings-advanced]
|
|
570
|
+
[data-prts-settings-advanced] > [data-prts-section-heading] strong,
|
|
619
571
|
[data-prts-preset-card] strong,
|
|
620
572
|
[data-prts-settings-common] [data-prts-setting-row] > span > strong,
|
|
621
573
|
[data-prts-settings-advanced-content] section > header strong,
|
|
@@ -812,7 +764,7 @@ html[data-dsh-prts] [data-prts-theme-settings] :is([data-prts-settings-presets],
|
|
|
812
764
|
border: 0;
|
|
813
765
|
}
|
|
814
766
|
html[data-dsh-prts] [data-prts-theme-settings] :is([data-prts-settings-presets], [data-prts-settings-common]) > header strong,
|
|
815
|
-
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-advanced]
|
|
767
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-advanced] > [data-prts-section-heading] strong {
|
|
816
768
|
font: 650 16px/1.5 var(--prts-body);
|
|
817
769
|
letter-spacing: 0;
|
|
818
770
|
}
|
|
@@ -869,16 +821,31 @@ html[data-dsh-prts] [data-prts-theme-settings] button[data-prts-setting-key].is-
|
|
|
869
821
|
border-color: color-mix(in srgb, var(--prts-yellow) 70%, transparent);
|
|
870
822
|
color: var(--prts-ink);
|
|
871
823
|
}
|
|
872
|
-
html[data-dsh-prts] [data-prts-theme-settings]
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
824
|
+
html[data-dsh-prts] [data-prts-theme-settings] {
|
|
825
|
+
& [data-prts-setting-row="sessionFlowPalette"] > div { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
826
|
+
& [data-prts-setting-row="sessionFlowColors"] { grid-template-columns: minmax(0, 1fr); }
|
|
827
|
+
& [data-prts-settings-advanced] { border-top: 1px solid var(--prts-line); }
|
|
828
|
+
& [data-prts-settings-advanced] > [data-prts-section-heading] { padding: 22px 0; }
|
|
829
|
+
& [data-prts-settings-section][hidden] { display: none !important; }
|
|
830
|
+
& [data-prts-settings-advanced-content] { border: 0; }
|
|
831
|
+
& [data-prts-settings-advanced-content] > section { padding: 0 0 20px; }
|
|
832
|
+
& :is([data-prts-advanced-summary], [data-prts-session-flow-speed-output], [data-prts-particle-traversal-output]) { color: var(--prts-muted); font-size: 12px; font-weight: 400; }
|
|
833
|
+
& [data-prts-settings-sidebar] { display:flex; flex-direction:column; min-height:0; position:relative; border-right:1px solid var(--prts-line); }
|
|
834
|
+
& [data-prts-settings-nav] { border:0; background:none; }
|
|
835
|
+
& [data-prts-settings-actions] { margin:auto 0 0; padding:14px; border:0; min-height:0; }
|
|
836
|
+
& [data-prts-reset-zone] { min-height:0; }
|
|
837
|
+
& [data-prts-reset-visual] { color:var(--prts-muted); box-shadow:none; background:none; font-size:12px; }
|
|
838
|
+
& [data-prts-reset-confirm], & [data-prts-persistence-error] { display:flex; gap:8px; align-items:center; position:absolute; left:12px; bottom:52px; width:270px; padding:16px; border:1px solid var(--prts-line); border-radius:6px; background:var(--prts-panel-raised); box-shadow:0 8px 24px #0004; z-index:5; flex-wrap:wrap; font:400 12px/1.6 var(--prts-body); }
|
|
839
|
+
& [data-prts-reset-confirm] > span { flex-basis:100%; }
|
|
840
|
+
@media(max-width:720px) {
|
|
841
|
+
& [data-prts-settings-sidebar] { flex-direction:row; border:0; border-bottom:1px solid var(--prts-line); }
|
|
842
|
+
& [data-prts-settings-nav] { flex:1; padding:8px 4px; border:0; }
|
|
843
|
+
& [data-prts-settings-jump] { padding:10px 8px; }
|
|
844
|
+
& [data-prts-settings-actions] { margin:0; padding:8px 4px; align-self:center; }
|
|
845
|
+
& [data-prts-reset-confirm], & [data-prts-persistence-error] { left:auto; right:8px; top:100%; bottom:auto; max-width:calc(100vw - 32px); }
|
|
846
|
+
}
|
|
847
|
+
& [data-prts-reset-zone] { padding: 0; }
|
|
848
|
+
}
|
|
882
849
|
@media (max-width: 720px) {
|
|
883
850
|
html[data-dsh-prts] [data-prts-theme-settings] { width: 100vw; height: 100dvh; max-height: none; }
|
|
884
851
|
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-frame] { border-radius: 0; }
|
|
@@ -894,12 +861,13 @@ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-reset-zone] { padding:
|
|
|
894
861
|
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row]:has(> [data-prts-setting-switch]) { grid-template-columns: minmax(0, 1fr) auto; gap: 20px; }
|
|
895
862
|
}
|
|
896
863
|
|
|
864
|
+
html[data-dsh-prts] [data-prts-theme-settings] {
|
|
897
865
|
/* Four navigation destinations, each with one continuous settings surface. */
|
|
898
|
-
|
|
866
|
+
& [data-prts-theme-settings-scroll] {
|
|
899
867
|
padding: 20px 24px 28px;
|
|
900
868
|
background: var(--prts-canvas);
|
|
901
869
|
}
|
|
902
|
-
|
|
870
|
+
& [data-prts-settings-section] {
|
|
903
871
|
margin: 0 0 28px;
|
|
904
872
|
padding: 0 20px 8px;
|
|
905
873
|
border: 1px solid var(--prts-line);
|
|
@@ -907,7 +875,7 @@ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-section] {
|
|
|
907
875
|
background: var(--prts-panel);
|
|
908
876
|
scroll-margin-top: 16px;
|
|
909
877
|
}
|
|
910
|
-
|
|
878
|
+
& [data-prts-settings-section] > [data-prts-section-heading] {
|
|
911
879
|
display: flex;
|
|
912
880
|
align-items: center;
|
|
913
881
|
justify-content: space-between;
|
|
@@ -920,44 +888,73 @@ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-section] > [d
|
|
|
920
888
|
border-radius: 5px 5px 0 0;
|
|
921
889
|
background: var(--prts-panel-raised);
|
|
922
890
|
}
|
|
923
|
-
|
|
891
|
+
& [data-prts-settings-section] > [data-prts-section-heading] > span {
|
|
924
892
|
display: grid;
|
|
925
893
|
gap: 5px;
|
|
926
894
|
}
|
|
927
|
-
|
|
895
|
+
& [data-prts-settings-section] > [data-prts-section-heading] strong {
|
|
928
896
|
font: 650 18px/1.4 var(--prts-body);
|
|
929
897
|
letter-spacing: 0;
|
|
930
898
|
}
|
|
931
|
-
|
|
899
|
+
& [data-prts-settings-section] > [data-prts-section-heading] small {
|
|
932
900
|
color: var(--prts-muted);
|
|
933
901
|
font: 400 12px/1.5 var(--prts-body);
|
|
934
902
|
}
|
|
935
|
-
|
|
903
|
+
& [data-prts-settings-section="appearance"] > section {
|
|
936
904
|
margin-bottom: 8px;
|
|
937
905
|
}
|
|
938
|
-
|
|
906
|
+
& [data-prts-settings-section="appearance"] > section > header {
|
|
939
907
|
padding: 16px 0 10px;
|
|
940
908
|
}
|
|
941
|
-
|
|
909
|
+
& [data-prts-settings-section="appearance"] > section > header strong {
|
|
942
910
|
font-size: 13px;
|
|
943
911
|
color: var(--prts-muted);
|
|
944
912
|
}
|
|
945
|
-
|
|
946
|
-
padding-bottom: 0;
|
|
947
|
-
}
|
|
948
|
-
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-advanced]:not([open]) > summary {
|
|
949
|
-
margin-bottom: 0;
|
|
950
|
-
border-bottom: 0;
|
|
951
|
-
border-radius: 5px;
|
|
952
|
-
}
|
|
953
|
-
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-jump][aria-current="location"] {
|
|
913
|
+
& [data-prts-settings-jump][aria-selected="true"] {
|
|
954
914
|
color: var(--prts-ink);
|
|
955
915
|
background: var(--prts-panel-raised);
|
|
956
916
|
box-shadow: inset 3px 0 var(--prts-yellow);
|
|
957
917
|
}
|
|
958
918
|
@media (max-width: 720px) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
919
|
+
& [data-prts-theme-settings-scroll] { padding: 16px 12px 24px; }
|
|
920
|
+
& [data-prts-settings-section] { padding-inline: 14px; margin-bottom: 24px; }
|
|
921
|
+
& [data-prts-settings-section] > [data-prts-section-heading] { margin-inline: -14px; padding-inline: 14px; flex-wrap: wrap; gap: 4px 12px; }
|
|
922
|
+
& [data-prts-settings-jump][aria-selected="true"] { box-shadow: inset 0 -3px var(--prts-yellow); }
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
& [data-prts-custom-emblem] {
|
|
926
|
+
display: grid;
|
|
927
|
+
gap: 14px;
|
|
928
|
+
padding-block: 16px 24px;
|
|
929
|
+
border-bottom: 1px solid var(--prts-line);
|
|
930
|
+
margin-bottom: 20px;
|
|
931
|
+
}
|
|
932
|
+
& [data-prts-custom-emblem] header { display: grid; gap: 6px; }
|
|
933
|
+
& [data-prts-custom-emblem] :is(small, p) { font: 400 12px/1.6 var(--prts-body); color: var(--prts-muted); margin: 0; }
|
|
934
|
+
& [data-prts-custom-emblem] label { display: grid; grid-template-columns: 110px minmax(0, 1fr); align-items: center; gap: 12px; font-size: 13px; }
|
|
935
|
+
& [data-prts-emblem-editor] { display: grid; gap: 14px; }
|
|
936
|
+
& [data-prts-custom-emblem] :is(input,select) { min-width: 0; max-width: 100%; }
|
|
937
|
+
& [data-prts-custom-emblem] select { padding: 8px; background: var(--prts-panel-raised); color: var(--prts-ink); border: 1px solid var(--prts-line); }
|
|
938
|
+
& [data-prts-emblem-preview] { width: min(100%, 280px); height: auto; justify-self: center; border: 1px solid var(--prts-line); border-radius: 4px; }
|
|
939
|
+
& [data-prts-emblem-actions] { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
940
|
+
& [data-prts-emblem-actions] button { padding: 10px 14px; border: 1px solid var(--prts-line); border-radius: 4px; color: var(--prts-ink); background: var(--prts-panel-raised); }
|
|
941
|
+
& [data-prts-emblem-actions] button:disabled { opacity: .45; }
|
|
942
|
+
& [data-prts-custom-emblem] h4 { margin: 8px 0 12px; font: 600 14px/1.5 var(--prts-body); }
|
|
943
|
+
& [data-prts-emblem-list] { display: grid; gap: 10px; list-style: none; padding: 0; margin: 14px 0; }
|
|
944
|
+
& :is([data-prts-emblem-list] > li, [data-prts-emblem-hero-row]) { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; padding: 12px; border: 1px solid var(--prts-line); border-radius: 4px; }
|
|
945
|
+
& :is([data-prts-emblem-list], [data-prts-emblem-hero-row]) canvas { width: 48px; height: 48px; flex: none; border-radius: 3px; }
|
|
946
|
+
& :is([data-prts-emblem-list] > li, [data-prts-emblem-hero-row]) > span { flex: 1; min-width: 100px; overflow-wrap: anywhere; font-size: 13px; }
|
|
947
|
+
& [data-emblem-action] { padding: 7px 10px; border: 1px solid var(--prts-line); border-radius: 4px; background: var(--prts-panel-raised); color: var(--prts-ink); }
|
|
948
|
+
& [data-emblem-action]:disabled { opacity: .4; }
|
|
949
|
+
& [data-prts-emblem-reorder] { padding: 0; border: 0; background: transparent; cursor: grab; }
|
|
950
|
+
& [data-emblem-key] {user-select:none;-webkit-touch-callout:none}
|
|
951
|
+
& [data-prts-emblem-reorder]:focus-visible { outline: 2px solid var(--prts-ink); outline-offset: 3px; }
|
|
952
|
+
& [data-prts-emblem-sorting] { user-select: none; }
|
|
953
|
+
& [data-prts-emblem-dragging] { opacity: .35; border-style: dashed !important; }
|
|
954
|
+
& [data-prts-emblem-drag-ghost] { background: var(--prts-panel-raised); box-shadow: 0 8px 28px #0005; opacity: .95; }
|
|
955
|
+
& [data-prts-emblem-workspace] { display: grid; gap: 14px; padding: 16px 0; border-top: 1px solid var(--prts-line); }
|
|
956
|
+
@media (max-width:720px) {
|
|
957
|
+
& [data-prts-emblem-list] [data-prts-emblem-actions] { flex-basis: 100%; gap: 5px; }
|
|
958
|
+
}
|
|
959
|
+
|
|
963
960
|
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
const RESIZE_SHIELD_ATTRIBUTE = 'data-prts-resize-shield'
|
|
2
|
-
const DEFAULT_SETTLE_DELAY = 180
|
|
3
|
-
|
|
4
|
-
export function createResizeShieldAdapter({
|
|
5
|
-
window,
|
|
6
|
-
settleDelay = DEFAULT_SETTLE_DELAY,
|
|
7
|
-
} = {}) {
|
|
8
|
-
let targets = []
|
|
9
|
-
let settleTimer
|
|
10
|
-
let started = false
|
|
11
|
-
|
|
12
|
-
function setActive(active) {
|
|
13
|
-
for (const node of targets) {
|
|
14
|
-
if (!node || node.hasAttribute?.(RESIZE_SHIELD_ATTRIBUTE) === active) continue
|
|
15
|
-
node.toggleAttribute?.(RESIZE_SHIELD_ATTRIBUTE, active)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function clearTimer() {
|
|
20
|
-
if (settleTimer === undefined) return
|
|
21
|
-
window?.clearTimeout?.(settleTimer)
|
|
22
|
-
settleTimer = undefined
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function settle() {
|
|
26
|
-
settleTimer = undefined
|
|
27
|
-
setActive(false)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function onResize() {
|
|
31
|
-
if (!started) return
|
|
32
|
-
if (Number(window?.innerWidth) <= 640) {
|
|
33
|
-
clearTimer()
|
|
34
|
-
setActive(false)
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
setActive(true)
|
|
38
|
-
clearTimer()
|
|
39
|
-
settleTimer = window?.setTimeout?.(settle, settleDelay)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function dispose() {
|
|
43
|
-
clearTimer()
|
|
44
|
-
window?.removeEventListener?.('resize', onResize)
|
|
45
|
-
setActive(false)
|
|
46
|
-
targets = []
|
|
47
|
-
started = false
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
start(nextTargets = []) {
|
|
52
|
-
const resolved = [...new Set(nextTargets)].filter(Boolean)
|
|
53
|
-
if (!window || !resolved.length) {
|
|
54
|
-
dispose()
|
|
55
|
-
return false
|
|
56
|
-
}
|
|
57
|
-
if (started) {
|
|
58
|
-
clearTimer()
|
|
59
|
-
setActive(false)
|
|
60
|
-
window.removeEventListener?.('resize', onResize)
|
|
61
|
-
}
|
|
62
|
-
targets = resolved
|
|
63
|
-
started = true
|
|
64
|
-
window.addEventListener?.('resize', onResize, { passive: true })
|
|
65
|
-
return true
|
|
66
|
-
},
|
|
67
|
-
dispose,
|
|
68
|
-
}
|
|
69
|
-
}
|