@ash-qw/dsh-theme-prts 0.1.91 → 0.1.93
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 +31 -0
- package/README.en.md +23 -35
- package/README.md +23 -37
- package/lib/client.js +1018 -50
- package/package.json +15 -3
- package/src/client/conversation-scale-adapter.js +77 -0
- package/src/client/index.js +18 -3
- package/src/client/operations-shell-v2.js +214 -16
- package/src/client/particle-field-adapter.js +89 -9
- package/src/client/preferences.js +24 -5
- package/src/client/theme-controller-v2.js +609 -16
- package/src/client/theme-settings-workbench.js +69 -2
- package/src/styles/prts.css +362 -5
- package/src/styles/theme-settings-workbench.css +17 -0
|
@@ -9,6 +9,38 @@ const ROOT_ATTRIBUTES = [
|
|
|
9
9
|
'data-prts-particle-pattern',
|
|
10
10
|
]
|
|
11
11
|
|
|
12
|
+
const TRANSITION_ATTRIBUTE = 'data-prts-scheme-transition'
|
|
13
|
+
const TRANSITION_MODE_ATTRIBUTE = 'data-prts-scheme-transition-mode'
|
|
14
|
+
const TRANSITION_INTERACTIVE_ATTRIBUTE = 'data-prts-scheme-transition-interactive'
|
|
15
|
+
const TRANSITION_ARMED_ATTRIBUTE = 'data-prts-scheme-transition-armed'
|
|
16
|
+
const TRANSITION_COMPLETE_ATTRIBUTE = 'data-prts-scheme-transition-complete'
|
|
17
|
+
const TRANSITION_PROPERTIES = [
|
|
18
|
+
'--prts-scheme-origin-x',
|
|
19
|
+
'--prts-scheme-origin-y',
|
|
20
|
+
'--prts-scheme-duration',
|
|
21
|
+
'--prts-scheme-reveal-x',
|
|
22
|
+
'--prts-scheme-control-progress',
|
|
23
|
+
'--prts-scheme-control-meter-height',
|
|
24
|
+
'--prts-scheme-control-value-image',
|
|
25
|
+
]
|
|
26
|
+
const REVEAL_EDGE_OVERSCAN = 32
|
|
27
|
+
const REVEAL_GRID_WIDTH = 220
|
|
28
|
+
const REVEAL_TIMING_LIMITS = {
|
|
29
|
+
desktop: { minimum: 480, maximum: 560, widthFactor: .38 },
|
|
30
|
+
mobile: { minimum: 320, maximum: 380, widthFactor: .9 },
|
|
31
|
+
}
|
|
32
|
+
const revealValueImages = new Map()
|
|
33
|
+
|
|
34
|
+
function revealValueImageUrl(value) {
|
|
35
|
+
const percentage = Math.min(100, Math.max(0, Math.round(Number(value) || 0)))
|
|
36
|
+
if (revealValueImages.has(percentage)) return revealValueImages.get(percentage)
|
|
37
|
+
const label = String(percentage).padStart(3, '0')
|
|
38
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="11" viewBox="0 0 24 11"><text x="0" y="9" fill="#ffd400" font-family="monospace" font-size="10" font-weight="700">${label}</text></svg>`
|
|
39
|
+
const url = `url("data:image/svg+xml,${encodeURIComponent(svg)}")`
|
|
40
|
+
revealValueImages.set(percentage, url)
|
|
41
|
+
return url
|
|
42
|
+
}
|
|
43
|
+
|
|
12
44
|
function themeIdentity(value) {
|
|
13
45
|
if (typeof value === 'string') return value
|
|
14
46
|
if (!value || typeof value !== 'object') return ''
|
|
@@ -25,23 +57,57 @@ function resolveScheme(value) {
|
|
|
25
57
|
return identity.includes('dark') || identity.includes('night') ? 'dark' : 'light'
|
|
26
58
|
}
|
|
27
59
|
|
|
28
|
-
export function createThemeController({ document, window, cssText, service }) {
|
|
29
|
-
if (!document || !window)
|
|
60
|
+
export function createThemeController({ document, window, cssText, service, onTransitionStateChange = () => {} }) {
|
|
61
|
+
if (!document || !window) {
|
|
62
|
+
const gesture = { update() {}, finish() {}, cancel() {} }
|
|
63
|
+
return { apply() {}, sync() {}, refresh() {}, setTheme() {}, beginThemeTransition() { return gesture }, toggle() {}, dispose() {} }
|
|
64
|
+
}
|
|
30
65
|
|
|
31
66
|
const root = document.documentElement
|
|
32
67
|
let style
|
|
33
68
|
let disposed = false
|
|
34
69
|
let refreshRevision = 0
|
|
35
70
|
let setRevision = 0
|
|
71
|
+
let transitionRevision = 0
|
|
72
|
+
let activeTransition
|
|
73
|
+
|
|
74
|
+
let transitionStateActive = false
|
|
75
|
+
|
|
76
|
+
function setTransitionState(active) {
|
|
77
|
+
const next = Boolean(active)
|
|
78
|
+
if (transitionStateActive === next) return
|
|
79
|
+
transitionStateActive = next
|
|
80
|
+
try { onTransitionStateChange(next) } catch {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function clearTransitionState() {
|
|
84
|
+
root.removeAttribute(TRANSITION_ATTRIBUTE)
|
|
85
|
+
root.removeAttribute(TRANSITION_MODE_ATTRIBUTE)
|
|
86
|
+
root.removeAttribute(TRANSITION_INTERACTIVE_ATTRIBUTE)
|
|
87
|
+
root.removeAttribute(TRANSITION_ARMED_ATTRIBUTE)
|
|
88
|
+
root.removeAttribute(TRANSITION_COMPLETE_ATTRIBUTE)
|
|
89
|
+
for (const property of TRANSITION_PROPERTIES) root.style.removeProperty(property)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function cancelActiveTransition({ keepPaused = false } = {}) {
|
|
93
|
+
const current = activeTransition
|
|
94
|
+
activeTransition = undefined
|
|
95
|
+
current?.cancel?.()
|
|
96
|
+
current?.viewTransition?.skipTransition?.()
|
|
97
|
+
clearTransitionState()
|
|
98
|
+
if (!keepPaused) setTransitionState(false)
|
|
99
|
+
}
|
|
36
100
|
|
|
37
101
|
function clearThemeState() {
|
|
38
102
|
for (const attribute of ROOT_ATTRIBUTES) root.removeAttribute(attribute)
|
|
103
|
+
clearTransitionState()
|
|
39
104
|
}
|
|
40
105
|
|
|
41
106
|
function removeOwnedState() {
|
|
42
107
|
disposed = true
|
|
43
108
|
refreshRevision += 1
|
|
44
109
|
setRevision += 1
|
|
110
|
+
cancelActiveTransition()
|
|
45
111
|
clearThemeState()
|
|
46
112
|
root.removeAttribute('data-dsh-prts-settings')
|
|
47
113
|
style?.remove()
|
|
@@ -59,9 +125,22 @@ export function createThemeController({ document, window, cssText, service }) {
|
|
|
59
125
|
root.setAttribute('data-dsh-prts-settings', '')
|
|
60
126
|
}
|
|
61
127
|
|
|
128
|
+
function applyScheme(value) {
|
|
129
|
+
if (disposed) return
|
|
130
|
+
const next = resolveScheme(value)
|
|
131
|
+
root.dataset.prtsScheme = next
|
|
132
|
+
return next
|
|
133
|
+
}
|
|
134
|
+
|
|
62
135
|
function sync(value) {
|
|
63
136
|
if (disposed) return
|
|
64
|
-
|
|
137
|
+
const next = resolveScheme(value)
|
|
138
|
+
if (activeTransition) {
|
|
139
|
+
if (next === activeTransition.target) return root.dataset.prtsScheme
|
|
140
|
+
if (activeTransition.interactive && next === activeTransition.source) return root.dataset.prtsScheme
|
|
141
|
+
if (next !== root.dataset.prtsScheme) cancelActiveTransition()
|
|
142
|
+
}
|
|
143
|
+
return applyScheme(next)
|
|
65
144
|
}
|
|
66
145
|
|
|
67
146
|
function fallbackTheme() {
|
|
@@ -84,25 +163,537 @@ export function createThemeController({ document, window, cssText, service }) {
|
|
|
84
163
|
return value
|
|
85
164
|
}
|
|
86
165
|
|
|
87
|
-
function
|
|
166
|
+
function prefersReducedMotion() {
|
|
167
|
+
if (root.dataset.prtsMotion === 'reduced') return true
|
|
168
|
+
try { return window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true } catch { return false }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function canAnimateScheme(options) {
|
|
172
|
+
const animationAvailable = typeof document.startViewTransition === 'function'
|
|
173
|
+
&& typeof root.animate === 'function'
|
|
174
|
+
return options?.animate === true
|
|
175
|
+
&& animationAvailable
|
|
176
|
+
&& !prefersReducedMotion()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function transitionOrigin(origin) {
|
|
180
|
+
const width = Math.max(Number(window.innerWidth) || 0, Number(root.clientWidth) || 0)
|
|
181
|
+
const height = Math.max(Number(window.innerHeight) || 0, Number(root.clientHeight) || 0)
|
|
182
|
+
const clamp = (value, maximum, fallback) => Math.min(maximum, Math.max(0, Number.isFinite(Number(value)) ? Number(value) : fallback))
|
|
183
|
+
const x = clamp(origin?.x, width, width / 2)
|
|
184
|
+
const y = clamp(origin?.y, height, height / 2)
|
|
185
|
+
return { x, y }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function revealViewport(origin) {
|
|
189
|
+
const geometry = transitionOrigin(origin)
|
|
190
|
+
const width = Math.max(1, Number(window.innerWidth) || 0, Number(root.clientWidth) || 0)
|
|
191
|
+
const height = Math.max(1, Number(window.innerHeight) || 0, Number(root.clientHeight) || 0)
|
|
192
|
+
return { ...geometry, width, height }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function revealDuration(width) {
|
|
196
|
+
const limits = width <= 720 ? REVEAL_TIMING_LIMITS.mobile : REVEAL_TIMING_LIMITS.desktop
|
|
197
|
+
return Math.min(limits.maximum, Math.max(limits.minimum, Math.round(width * limits.widthFactor)))
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function revealProgress(value) {
|
|
201
|
+
const numeric = Number(value)
|
|
202
|
+
if (!Number.isFinite(numeric)) return 0
|
|
203
|
+
return Math.min(1, Math.max(0, numeric))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function removeRevealHud(session) {
|
|
207
|
+
const hud = session?.hud
|
|
208
|
+
if (!hud) return
|
|
209
|
+
try {
|
|
210
|
+
if (hud.matches?.(':popover-open')) hud.hidePopover?.()
|
|
211
|
+
} catch {}
|
|
212
|
+
hud.remove()
|
|
213
|
+
session.hud = undefined
|
|
214
|
+
session.hudGrid = undefined
|
|
215
|
+
session.hudEdge = undefined
|
|
216
|
+
session.hudMark = undefined
|
|
217
|
+
session.hudMeterFill = undefined
|
|
218
|
+
session.hudLabel = undefined
|
|
219
|
+
session.hudValue = undefined
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function updateRevealHud(session) {
|
|
223
|
+
if (!session || session.settled) return
|
|
224
|
+
const percentage = Math.round(revealProgress(session.progress) * 100)
|
|
225
|
+
if (session.hudEdge) session.hudEdge.dataset.prtsSchemeRevealSide = 'left'
|
|
226
|
+
root.style.setProperty('--prts-scheme-control-progress', String(session.progress))
|
|
227
|
+
root.style.setProperty('--prts-scheme-control-meter-height', `${revealProgress(session.progress) * 48}px`)
|
|
228
|
+
root.style.setProperty('--prts-scheme-control-value-image', revealValueImageUrl(percentage))
|
|
229
|
+
root.toggleAttribute(TRANSITION_ARMED_ATTRIBUTE, session.interactive && session.progress >= .5)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function releaseRevealHud(session) {
|
|
233
|
+
if (!session?.hud || session.settled) return Promise.resolve()
|
|
234
|
+
session.hud.dataset.prtsSchemeRevealState = 'complete'
|
|
235
|
+
root.setAttribute(TRANSITION_COMPLETE_ATTRIBUTE, '')
|
|
236
|
+
return new Promise(resolve => window.setTimeout(resolve, 100))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function mountRevealHud(session) {
|
|
240
|
+
if (session.settled || session.hud || !document.body) return
|
|
241
|
+
const hud = document.createElement('div')
|
|
242
|
+
hud.dataset.prtsSchemeReveal = session.target
|
|
243
|
+
hud.dataset.prtsSchemeRevealMode = session.interactive ? 'interactive' : 'automatic'
|
|
244
|
+
hud.setAttribute('aria-hidden', 'true')
|
|
245
|
+
hud.setAttribute('popover', 'manual')
|
|
246
|
+
const grid = document.createElement('i')
|
|
247
|
+
grid.dataset.prtsSchemeRevealGrid = ''
|
|
248
|
+
const edge = document.createElement('span')
|
|
249
|
+
edge.dataset.prtsSchemeRevealEdge = ''
|
|
250
|
+
const blend = document.createElement('i')
|
|
251
|
+
blend.dataset.prtsSchemeRevealBlend = ''
|
|
252
|
+
const node = document.createElement('span')
|
|
253
|
+
node.dataset.prtsSchemeRevealNode = ''
|
|
254
|
+
const meter = document.createElement('i')
|
|
255
|
+
meter.dataset.prtsSchemeRevealMeter = ''
|
|
256
|
+
const meterFill = document.createElement('i')
|
|
257
|
+
meterFill.dataset.prtsSchemeRevealMeterFill = ''
|
|
258
|
+
const label = document.createElement('span')
|
|
259
|
+
label.dataset.prtsSchemeRevealLabel = ''
|
|
260
|
+
label.textContent = 'CTRL'
|
|
261
|
+
const mark = document.createElement('i')
|
|
262
|
+
mark.dataset.prtsSchemeRevealMark = ''
|
|
263
|
+
const value = document.createElement('span')
|
|
264
|
+
value.dataset.prtsSchemeRevealValue = ''
|
|
265
|
+
meter.appendChild(meterFill)
|
|
266
|
+
node.append(meter, label, value, mark)
|
|
267
|
+
edge.append(grid, blend, node)
|
|
268
|
+
hud.appendChild(edge)
|
|
269
|
+
document.body.appendChild(hud)
|
|
270
|
+
session.hud = hud
|
|
271
|
+
session.hudGrid = grid
|
|
272
|
+
session.hudEdge = edge
|
|
273
|
+
session.hudMark = mark
|
|
274
|
+
session.hudMeterFill = meterFill
|
|
275
|
+
session.hudLabel = label
|
|
276
|
+
session.hudValue = value
|
|
277
|
+
updateRevealHud(session)
|
|
278
|
+
try { hud.showPopover?.() } catch {}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function revealTargetX(session, progress) {
|
|
282
|
+
if (progress === 0) return -REVEAL_EDGE_OVERSCAN
|
|
283
|
+
if (progress === 1) return session.geometry.width + REVEAL_EDGE_OVERSCAN
|
|
284
|
+
return progress * session.geometry.width
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function revealPixels(value) {
|
|
288
|
+
const rounded = Math.round(value * 1000) / 1000
|
|
289
|
+
return `${Object.is(rounded, -0) ? 0 : rounded}px`
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function revealClipPath(session, x) {
|
|
293
|
+
return `inset(0 ${revealPixels(Math.max(0, session.geometry.width - x))} 0 0)`
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function revealEdgeTransform(x) {
|
|
297
|
+
return `translate3d(${revealPixels(x - 1)}, 0, 0)`
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function revealGridPosition(x) {
|
|
301
|
+
return `${revealPixels(REVEAL_GRID_WIDTH - x)} 0px`
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function revealGroupTranslate(x) {
|
|
305
|
+
return `${revealPixels(x)} 0px`
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function revealNamedAnimation(keyframes, options, pseudoElement) {
|
|
309
|
+
return root.animate(keyframes, { ...options, pseudoElement })
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function createInteractiveScrub(session) {
|
|
313
|
+
if (!session.interactive || session.settled || session.scrubAnimations) return
|
|
314
|
+
try {
|
|
315
|
+
const options = {
|
|
316
|
+
duration: session.duration,
|
|
317
|
+
easing: 'linear',
|
|
318
|
+
fill: 'both',
|
|
319
|
+
}
|
|
320
|
+
const clip = revealNamedAnimation([
|
|
321
|
+
{ clipPath: revealClipPath(session, 0) },
|
|
322
|
+
{ clipPath: revealClipPath(session, session.geometry.width) },
|
|
323
|
+
], options, '::view-transition-new(root)')
|
|
324
|
+
const edge = revealNamedAnimation([
|
|
325
|
+
{ translate: revealGroupTranslate(0) },
|
|
326
|
+
{ translate: revealGroupTranslate(session.geometry.width) },
|
|
327
|
+
], options, '::view-transition-group(prts-scheme-edge)')
|
|
328
|
+
const node = revealNamedAnimation([
|
|
329
|
+
{ translate: revealGroupTranslate(0) },
|
|
330
|
+
{ translate: revealGroupTranslate(session.geometry.width) },
|
|
331
|
+
], options, '::view-transition-group(prts-scheme-node)')
|
|
332
|
+
const grid = revealNamedAnimation([
|
|
333
|
+
{ translate: revealGroupTranslate(0) },
|
|
334
|
+
{ translate: revealGroupTranslate(session.geometry.width) },
|
|
335
|
+
], options, '::view-transition-group(prts-scheme-grid)')
|
|
336
|
+
const gridPhase = revealNamedAnimation([
|
|
337
|
+
{ backgroundPosition: revealGridPosition(0) },
|
|
338
|
+
{ backgroundPosition: revealGridPosition(session.geometry.width) },
|
|
339
|
+
], options, '::view-transition-new(prts-scheme-grid)')
|
|
340
|
+
const mark = revealNamedAnimation([
|
|
341
|
+
{ offset: 0, translate: revealGroupTranslate(0), rotate: '45deg' },
|
|
342
|
+
{ offset: .49, translate: revealGroupTranslate(session.geometry.width * .49), rotate: '45deg' },
|
|
343
|
+
{ offset: .51, translate: revealGroupTranslate(session.geometry.width * .51), rotate: '225deg' },
|
|
344
|
+
{ offset: 1, translate: revealGroupTranslate(session.geometry.width), rotate: '225deg' },
|
|
345
|
+
], options, '::view-transition-group(prts-scheme-mark)')
|
|
346
|
+
session.scrubAnimations = [clip, edge, node, grid, gridPhase, mark]
|
|
347
|
+
for (const animation of session.scrubAnimations) {
|
|
348
|
+
animation.pause?.()
|
|
349
|
+
animation.currentTime = session.progress * session.duration
|
|
350
|
+
}
|
|
351
|
+
} catch {}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function setRevealProgress(session, value) {
|
|
355
|
+
if (!session || session.settled) return
|
|
356
|
+
session.progress = revealProgress(value)
|
|
357
|
+
session.revealX = session.progress * session.geometry.width
|
|
358
|
+
root.style.setProperty('--prts-scheme-reveal-x', `${session.revealX}px`)
|
|
359
|
+
if (session.scrubAnimations) {
|
|
360
|
+
try {
|
|
361
|
+
for (const animation of session.scrubAnimations) {
|
|
362
|
+
animation.pause?.()
|
|
363
|
+
animation.currentTime = session.progress * session.duration
|
|
364
|
+
}
|
|
365
|
+
} catch {}
|
|
366
|
+
}
|
|
367
|
+
updateRevealHud(session)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function cancelRevealAnimation(session) {
|
|
371
|
+
const animations = session?.progressAnimations || []
|
|
372
|
+
session.progressAnimations = undefined
|
|
373
|
+
for (const animation of animations) {
|
|
374
|
+
try { animation?.cancel?.() } catch {}
|
|
375
|
+
}
|
|
376
|
+
for (const animation of session?.completedAnimations || []) {
|
|
377
|
+
try { animation?.cancel?.() } catch {}
|
|
378
|
+
}
|
|
379
|
+
if (session) session.completedAnimations = undefined
|
|
380
|
+
for (const animation of session?.scrubAnimations || []) {
|
|
381
|
+
try { animation?.cancel?.() } catch {}
|
|
382
|
+
}
|
|
383
|
+
if (session) session.scrubAnimations = undefined
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function animateRevealTo(session, value, duration, easing) {
|
|
387
|
+
if (!session || session.settled) return Promise.resolve(false)
|
|
388
|
+
cancelRevealAnimation(session)
|
|
389
|
+
const targetProgress = revealProgress(value)
|
|
390
|
+
const targetX = revealTargetX(session, targetProgress)
|
|
391
|
+
if (!(duration > 0)) {
|
|
392
|
+
setRevealProgress(session, targetProgress)
|
|
393
|
+
return Promise.resolve(true)
|
|
394
|
+
}
|
|
395
|
+
let animations
|
|
396
|
+
try {
|
|
397
|
+
const options = {
|
|
398
|
+
duration: Math.round(duration),
|
|
399
|
+
easing,
|
|
400
|
+
fill: 'forwards',
|
|
401
|
+
}
|
|
402
|
+
const clip = root.animate([
|
|
403
|
+
{ clipPath: revealClipPath(session, session.revealX) },
|
|
404
|
+
{ clipPath: revealClipPath(session, targetX) },
|
|
405
|
+
], {
|
|
406
|
+
...options,
|
|
407
|
+
pseudoElement: '::view-transition-new(root)',
|
|
408
|
+
})
|
|
409
|
+
const edge = revealNamedAnimation([
|
|
410
|
+
{ translate: revealGroupTranslate(session.revealX) },
|
|
411
|
+
{ translate: revealGroupTranslate(targetX) },
|
|
412
|
+
], options, '::view-transition-group(prts-scheme-edge)')
|
|
413
|
+
const node = revealNamedAnimation([
|
|
414
|
+
{ translate: revealGroupTranslate(session.revealX) },
|
|
415
|
+
{ translate: revealGroupTranslate(targetX) },
|
|
416
|
+
], options, '::view-transition-group(prts-scheme-node)')
|
|
417
|
+
const grid = revealNamedAnimation([
|
|
418
|
+
{ translate: revealGroupTranslate(session.revealX) },
|
|
419
|
+
{ translate: revealGroupTranslate(targetX) },
|
|
420
|
+
], options, '::view-transition-group(prts-scheme-grid)')
|
|
421
|
+
const gridPhase = revealNamedAnimation([
|
|
422
|
+
{ backgroundPosition: revealGridPosition(session.revealX) },
|
|
423
|
+
{ backgroundPosition: revealGridPosition(targetX) },
|
|
424
|
+
], options, '::view-transition-new(prts-scheme-grid)')
|
|
425
|
+
const nodeProgress = revealNamedAnimation([
|
|
426
|
+
{ backgroundSize: `2px ${session.progress * 48}px` },
|
|
427
|
+
{ backgroundSize: `2px ${targetProgress * 48}px` },
|
|
428
|
+
], options, '::view-transition-new(prts-scheme-node)')
|
|
429
|
+
const mark = revealNamedAnimation([
|
|
430
|
+
{ translate: revealGroupTranslate(session.revealX), rotate: session.progress >= .5 ? '225deg' : '45deg' },
|
|
431
|
+
{ translate: revealGroupTranslate(targetX), rotate: targetProgress >= .5 ? '225deg' : '45deg' },
|
|
432
|
+
], options, '::view-transition-group(prts-scheme-mark)')
|
|
433
|
+
animations = [clip, edge, node, grid, gridPhase, nodeProgress, mark]
|
|
434
|
+
} catch {
|
|
435
|
+
setRevealProgress(session, targetProgress)
|
|
436
|
+
return Promise.resolve(true)
|
|
437
|
+
}
|
|
438
|
+
session.progressAnimations = animations
|
|
439
|
+
return Promise.all(animations.map(animation => Promise.resolve(animation?.finished).then(() => true, () => false))).then(results => {
|
|
440
|
+
const completed = results.every(Boolean)
|
|
441
|
+
if (!completed || session.settled || session.progressAnimations !== animations) return false
|
|
442
|
+
session.progressAnimations = undefined
|
|
443
|
+
session.completedAnimations = animations
|
|
444
|
+
session.progress = targetProgress
|
|
445
|
+
session.revealX = targetX
|
|
446
|
+
root.style.setProperty('--prts-scheme-reveal-x', `${session.revealX}px`)
|
|
447
|
+
updateRevealHud(session)
|
|
448
|
+
return true
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function requestHostScheme(value) {
|
|
453
|
+
try {
|
|
454
|
+
return Promise.resolve(service?.setTheme?.(value)).then(() => true, () => false)
|
|
455
|
+
} catch {
|
|
456
|
+
return Promise.resolve(false)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function restoreRevealSource(session) {
|
|
461
|
+
if (!session) return Promise.resolve(false)
|
|
462
|
+
session.restoreRequested = true
|
|
463
|
+
if (session.restorePromise) return session.restorePromise
|
|
464
|
+
session.restorePromise = Promise.resolve(session.hostRequest).then(async accepted => {
|
|
465
|
+
if (accepted && !(await requestHostScheme(session.source))) return false
|
|
466
|
+
applyScheme(session.source)
|
|
467
|
+
session.hostAccepted = false
|
|
468
|
+
return true
|
|
469
|
+
})
|
|
470
|
+
return session.restorePromise
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function finishRevealSession(session, { commit }) {
|
|
474
|
+
if (!session || session.settled) return
|
|
475
|
+
session.committed = commit
|
|
476
|
+
session.settled = true
|
|
477
|
+
cancelRevealAnimation(session)
|
|
478
|
+
if (!commit) applyScheme(session.source)
|
|
479
|
+
try { session.viewTransition?.skipTransition?.() } catch {}
|
|
480
|
+
removeRevealHud(session)
|
|
481
|
+
if (activeTransition?.token === session.token) activeTransition = undefined
|
|
482
|
+
clearTransitionState()
|
|
483
|
+
setTransitionState(false)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function createRevealSession(target, options = {}) {
|
|
487
|
+
cancelActiveTransition({ keepPaused: true })
|
|
488
|
+
setTransitionState(true)
|
|
489
|
+
const source = root.dataset.prtsScheme === 'dark' ? 'dark' : 'light'
|
|
490
|
+
const geometry = revealViewport(options.origin)
|
|
491
|
+
const interactive = options.interactive === true
|
|
492
|
+
const initialProgress = interactive ? revealProgress(options.progress) : 0
|
|
493
|
+
const duration = revealDuration(geometry.width)
|
|
494
|
+
const token = ++transitionRevision
|
|
495
|
+
let resolveHostReady
|
|
496
|
+
const session = {
|
|
497
|
+
token,
|
|
498
|
+
target,
|
|
499
|
+
source,
|
|
500
|
+
geometry,
|
|
501
|
+
interactive,
|
|
502
|
+
progress: initialProgress,
|
|
503
|
+
revealX: interactive ? initialProgress * geometry.width : -REVEAL_EDGE_OVERSCAN,
|
|
504
|
+
duration,
|
|
505
|
+
settled: false,
|
|
506
|
+
committed: false,
|
|
507
|
+
hostAccepted: false,
|
|
508
|
+
hostReady: new Promise(resolve => { resolveHostReady = resolve }),
|
|
509
|
+
}
|
|
510
|
+
root.style.setProperty('--prts-scheme-origin-x', `${geometry.x}px`)
|
|
511
|
+
root.style.setProperty('--prts-scheme-origin-y', `${geometry.y}px`)
|
|
512
|
+
root.style.setProperty('--prts-scheme-duration', `${interactive ? 3600000 : duration}ms`)
|
|
513
|
+
root.style.setProperty('--prts-scheme-reveal-x', `${session.revealX}px`)
|
|
514
|
+
root.style.setProperty('--prts-scheme-control-progress', String(initialProgress))
|
|
515
|
+
root.style.setProperty('--prts-scheme-control-meter-height', `${initialProgress * 48}px`)
|
|
516
|
+
root.style.setProperty('--prts-scheme-control-value-image', revealValueImageUrl(initialProgress * 100))
|
|
517
|
+
root.setAttribute(TRANSITION_ATTRIBUTE, target)
|
|
518
|
+
root.setAttribute(TRANSITION_MODE_ATTRIBUTE, 'view')
|
|
519
|
+
root.toggleAttribute(TRANSITION_INTERACTIVE_ATTRIBUTE, interactive)
|
|
520
|
+
|
|
521
|
+
session.cancel = () => {
|
|
522
|
+
if (session.settled) return
|
|
523
|
+
session.settled = true
|
|
524
|
+
cancelRevealAnimation(session)
|
|
525
|
+
if (session.interactive && !session.committed) void restoreRevealSource(session)
|
|
526
|
+
removeRevealHud(session)
|
|
527
|
+
}
|
|
528
|
+
activeTransition = session
|
|
529
|
+
|
|
530
|
+
let viewTransition
|
|
531
|
+
try {
|
|
532
|
+
viewTransition = document.startViewTransition(async () => {
|
|
533
|
+
session.hostRequest = requestHostScheme(target)
|
|
534
|
+
const accepted = await session.hostRequest
|
|
535
|
+
session.hostAccepted = accepted
|
|
536
|
+
if (!accepted) {
|
|
537
|
+
applyScheme(source)
|
|
538
|
+
resolveHostReady(false)
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
if (session.settled || session.restoreRequested) {
|
|
542
|
+
await restoreRevealSource(session)
|
|
543
|
+
resolveHostReady(false)
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
applyScheme(target)
|
|
547
|
+
mountRevealHud(session)
|
|
548
|
+
resolveHostReady(true)
|
|
549
|
+
})
|
|
550
|
+
} catch {
|
|
551
|
+
resolveHostReady(false)
|
|
552
|
+
if (activeTransition?.token === token) activeTransition = undefined
|
|
553
|
+
removeRevealHud(session)
|
|
554
|
+
clearTransitionState()
|
|
555
|
+
applyScheme(source)
|
|
556
|
+
setTransitionState(false)
|
|
557
|
+
return undefined
|
|
558
|
+
}
|
|
559
|
+
session.viewTransition = viewTransition
|
|
560
|
+
session.ready = Promise.all([
|
|
561
|
+
Promise.resolve(viewTransition?.ready).then(() => true, () => false),
|
|
562
|
+
session.hostReady,
|
|
563
|
+
]).then(([viewReady, hostReady]) => {
|
|
564
|
+
const ready = viewReady && hostReady
|
|
565
|
+
if (ready && activeTransition?.token === token && !session.settled) {
|
|
566
|
+
createInteractiveScrub(session)
|
|
567
|
+
}
|
|
568
|
+
return ready
|
|
569
|
+
})
|
|
570
|
+
return session
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function commitRevealScheme(target, origin) {
|
|
574
|
+
const session = createRevealSession(target, { origin })
|
|
575
|
+
if (!session) return root.dataset.prtsScheme
|
|
576
|
+
const ready = await session.ready
|
|
577
|
+
if (!ready) {
|
|
578
|
+
if (!session.settled) finishRevealSession(session, { commit: false })
|
|
579
|
+
return session.source
|
|
580
|
+
}
|
|
581
|
+
if (activeTransition?.token !== session.token || session.settled) return root.dataset.prtsScheme
|
|
582
|
+
await animateRevealTo(session, 1, session.duration, 'cubic-bezier(.4, 0, .2, 1)')
|
|
583
|
+
await releaseRevealHud(session)
|
|
584
|
+
if (activeTransition?.token === session.token && !session.settled) finishRevealSession(session, { commit: true })
|
|
585
|
+
return target
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function fallbackGesture(target, source) {
|
|
589
|
+
let progress = 0
|
|
590
|
+
let settled = false
|
|
591
|
+
return {
|
|
592
|
+
update(value) { if (!settled) progress = revealProgress(value) },
|
|
593
|
+
finish(commit = progress >= .5) {
|
|
594
|
+
if (settled) return root.dataset.prtsScheme
|
|
595
|
+
settled = true
|
|
596
|
+
return commit ? setTheme(target, { animate: false }) : source
|
|
597
|
+
},
|
|
598
|
+
cancel() {
|
|
599
|
+
if (settled) return source
|
|
600
|
+
settled = true
|
|
601
|
+
return source
|
|
602
|
+
},
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function beginThemeTransition(id, options = {}) {
|
|
607
|
+
const target = id === 'dark' ? 'dark' : 'light'
|
|
608
|
+
const source = root.dataset.prtsScheme === 'dark' ? 'dark' : 'light'
|
|
609
|
+
if (disposed || target === source || !canAnimateScheme({ ...options, animate: true })) return fallbackGesture(target, source)
|
|
610
|
+
const session = createRevealSession(target, { ...options, interactive: true })
|
|
611
|
+
if (!session) return fallbackGesture(target, source)
|
|
612
|
+
let finishPromise
|
|
613
|
+
|
|
614
|
+
const finish = commit => {
|
|
615
|
+
if (finishPromise) return finishPromise
|
|
616
|
+
if (session.settled || activeTransition?.token !== session.token) return Promise.resolve(root.dataset.prtsScheme)
|
|
617
|
+
root.removeAttribute(TRANSITION_INTERACTIVE_ATTRIBUTE)
|
|
618
|
+
root.toggleAttribute(TRANSITION_ARMED_ATTRIBUTE, commit)
|
|
619
|
+
if (session.hud) session.hud.dataset.prtsSchemeRevealMode = 'settling'
|
|
620
|
+
const visualDuration = commit
|
|
621
|
+
? Math.max(90, Math.round(session.duration * (1 - session.progress)))
|
|
622
|
+
: Math.max(90, Math.round(session.duration * session.progress * .72))
|
|
623
|
+
const visual = session.ready.then(ready => {
|
|
624
|
+
if (!ready || session.settled) return false
|
|
625
|
+
return animateRevealTo(
|
|
626
|
+
session,
|
|
627
|
+
commit ? 1 : 0,
|
|
628
|
+
visualDuration,
|
|
629
|
+
commit ? 'cubic-bezier(.22, 1, .36, 1)' : 'cubic-bezier(.4, 0, .2, 1)',
|
|
630
|
+
)
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
if (!commit) {
|
|
634
|
+
finishPromise = visual.then(async () => {
|
|
635
|
+
await releaseRevealHud(session)
|
|
636
|
+
await restoreRevealSource(session)
|
|
637
|
+
if (!session.settled) finishRevealSession(session, { commit: false })
|
|
638
|
+
return session.source
|
|
639
|
+
})
|
|
640
|
+
return finishPromise
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
finishPromise = visual.then(async () => {
|
|
644
|
+
if (!session.hostAccepted) {
|
|
645
|
+
if (!session.settled) finishRevealSession(session, { commit: false })
|
|
646
|
+
refresh()
|
|
647
|
+
return root.dataset.prtsScheme
|
|
648
|
+
}
|
|
649
|
+
await releaseRevealHud(session)
|
|
650
|
+
if (!session.settled) finishRevealSession(session, { commit: true })
|
|
651
|
+
return target
|
|
652
|
+
})
|
|
653
|
+
return finishPromise
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
update(progress) {
|
|
658
|
+
if (session.settled || activeTransition?.token !== session.token) return
|
|
659
|
+
setRevealProgress(session, progress)
|
|
660
|
+
},
|
|
661
|
+
finish,
|
|
662
|
+
cancel() { return finish(false) },
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function commitAnimatedScheme(target, origin) {
|
|
667
|
+
return commitRevealScheme(target, origin)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function setTheme(id, options = {}) {
|
|
88
671
|
const target = id === 'dark' ? 'dark' : 'light'
|
|
89
672
|
const revision = ++setRevision
|
|
90
|
-
|
|
91
|
-
|
|
673
|
+
const animate = canAnimateScheme(options) && target !== root.dataset.prtsScheme
|
|
674
|
+
|
|
675
|
+
if (animate) return commitAnimatedScheme(target, options.origin)
|
|
676
|
+
|
|
677
|
+
const commit = () => {
|
|
678
|
+
if (disposed || revision !== setRevision) return root.dataset.prtsScheme
|
|
679
|
+
return applyScheme(target)
|
|
680
|
+
}
|
|
681
|
+
const fail = () => {
|
|
682
|
+
if (disposed || revision !== setRevision) return root.dataset.prtsScheme
|
|
683
|
+
const result = refresh()
|
|
92
684
|
if (result && typeof result.then === 'function') {
|
|
93
|
-
return result.then(() =>
|
|
94
|
-
if (!disposed && revision === setRevision) sync(target)
|
|
95
|
-
return target
|
|
96
|
-
}).catch(() => {
|
|
97
|
-
if (!disposed && revision === setRevision) refresh()
|
|
98
|
-
return root.dataset.prtsScheme
|
|
99
|
-
})
|
|
685
|
+
return result.then(() => root.dataset.prtsScheme)
|
|
100
686
|
}
|
|
101
|
-
|
|
687
|
+
return root.dataset.prtsScheme
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
try {
|
|
691
|
+
const result = service?.setTheme?.(target)
|
|
692
|
+
if (result && typeof result.then === 'function') return result.then(commit).catch(fail)
|
|
693
|
+
return commit()
|
|
102
694
|
} catch {
|
|
103
|
-
|
|
695
|
+
return fail()
|
|
104
696
|
}
|
|
105
|
-
return target
|
|
106
697
|
}
|
|
107
698
|
|
|
108
699
|
function toggle() {
|
|
@@ -115,6 +706,7 @@ export function createThemeController({ document, window, cssText, service }) {
|
|
|
115
706
|
const preferences = normalizePreferences(value)
|
|
116
707
|
ensureStyle()
|
|
117
708
|
if (!preferences.enabled) {
|
|
709
|
+
cancelActiveTransition()
|
|
118
710
|
clearThemeState()
|
|
119
711
|
return
|
|
120
712
|
}
|
|
@@ -128,6 +720,7 @@ export function createThemeController({ document, window, cssText, service }) {
|
|
|
128
720
|
sync,
|
|
129
721
|
refresh,
|
|
130
722
|
setTheme,
|
|
723
|
+
beginThemeTransition,
|
|
131
724
|
toggle,
|
|
132
725
|
dispose: removeOwnedState,
|
|
133
726
|
}
|