@rafaelaugusto/jolt 0.1.0-rc.13 → 0.1.0-rc.14
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/README.md +2 -2
- package/dist/core/drop.d.ts +34 -1
- package/dist/core/drop.d.ts.map +1 -1
- package/dist/core/drop.js +93 -8
- package/dist/core/drop.js.map +1 -1
- package/dist/core/loading.d.ts +125 -0
- package/dist/core/loading.d.ts.map +1 -0
- package/dist/core/loading.js +119 -0
- package/dist/core/loading.js.map +1 -0
- package/dist/core/preset-recipe.d.ts +2 -2
- package/dist/core/preset-recipe.d.ts.map +1 -1
- package/dist/core/preset-recipe.js +3 -1
- package/dist/core/preset-recipe.js.map +1 -1
- package/dist/core/text-pieces.d.ts +18 -0
- package/dist/core/text-pieces.d.ts.map +1 -0
- package/dist/core/text-pieces.js +43 -0
- package/dist/core/text-pieces.js.map +1 -0
- package/dist/drag.d.ts +7 -1
- package/dist/drag.d.ts.map +1 -1
- package/dist/drag.js +72 -37
- package/dist/drag.js.map +1 -1
- package/dist/drop-zones.d.ts +1 -1
- package/dist/drop-zones.d.ts.map +1 -1
- package/dist/drop-zones.js +2 -0
- package/dist/drop-zones.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +7 -2
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +2 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +17 -15
- package/dist/loader.js.map +1 -1
- package/dist/reveal.d.ts.map +1 -1
- package/dist/reveal.js +4 -3
- package/dist/reveal.js.map +1 -1
- package/dist/shimmer.d.ts.map +1 -1
- package/dist/shimmer.js +6 -4
- package/dist/shimmer.js.map +1 -1
- package/dist/web/drag.d.ts +8 -0
- package/dist/web/drag.d.ts.map +1 -1
- package/dist/web/drag.js +68 -14
- package/dist/web/drag.js.map +1 -1
- package/dist/web/drop-zones.d.ts +2 -2
- package/dist/web/drop-zones.d.ts.map +1 -1
- package/dist/web/drop-zones.js +21 -4
- package/dist/web/drop-zones.js.map +1 -1
- package/dist/web/loading.d.ts +2 -1
- package/dist/web/loading.d.ts.map +1 -1
- package/dist/web/loading.js +19 -15
- package/dist/web/loading.js.map +1 -1
- package/dist/web/pointer.d.ts +2 -0
- package/dist/web/pointer.d.ts.map +1 -1
- package/dist/web/pointer.js +13 -0
- package/dist/web/pointer.js.map +1 -1
- package/dist/web/public.d.ts +1 -1
- package/dist/web/public.d.ts.map +1 -1
- package/dist/web/public.js.map +1 -1
- package/dist/web/reveal.d.ts.map +1 -1
- package/dist/web/reveal.js +4 -3
- package/dist/web/reveal.js.map +1 -1
- package/dist/web/text-effect.d.ts.map +1 -1
- package/dist/web/text-effect.js +10 -16
- package/dist/web/text-effect.js.map +1 -1
- package/package.json +12 -3
- package/platforms/android/java/org/rafaelaugusto/jolt/PointerInput.java +26 -4
- package/src/core/drop.ts +123 -5
- package/src/core/loading.ts +172 -0
- package/src/core/preset-recipe.ts +6 -2
- package/src/core/text-pieces.ts +52 -0
- package/src/drag.ts +81 -38
- package/src/drop-zones.ts +13 -3
- package/src/engine.ts +8 -3
- package/src/index.ts +3 -0
- package/src/loader.ts +23 -20
- package/src/reveal.ts +4 -3
- package/src/shimmer.ts +6 -4
- package/src/web/drag.ts +70 -12
- package/src/web/drop-zones.ts +31 -6
- package/src/web/loading.ts +38 -17
- package/src/web/pointer.ts +14 -0
- package/src/web/public.ts +3 -0
- package/src/web/reveal.ts +7 -8
- package/src/web/text-effect.ts +20 -17
package/src/web/drag.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface WebDragOptions {
|
|
|
26
26
|
rubberband?: boolean
|
|
27
27
|
/** Drive a borrowed numeric value instead of the input element's translation. */
|
|
28
28
|
value?: WebMotionValue
|
|
29
|
+
/** Element inside the dragged element that alone starts the drag; it may be a button. */
|
|
30
|
+
handle?: HTMLElement
|
|
29
31
|
onStart?: (event: WebDragEvent) => void
|
|
30
32
|
onMove?: (event: WebDragEvent) => void
|
|
31
33
|
onRelease?: (event: WebDragRelease) => void
|
|
@@ -86,6 +88,7 @@ export function dragOptions(options: WebDragOptions) {
|
|
|
86
88
|
'friction',
|
|
87
89
|
'rubberband',
|
|
88
90
|
'value',
|
|
91
|
+
'handle',
|
|
89
92
|
'onStart',
|
|
90
93
|
'onMove',
|
|
91
94
|
'onRelease',
|
|
@@ -197,14 +200,42 @@ export function assertDragSpace(target: HTMLElement, baselineIdentity: boolean)
|
|
|
197
200
|
}
|
|
198
201
|
}
|
|
199
202
|
|
|
203
|
+
interface DragAdapter {
|
|
204
|
+
shift(delta: DragPoint): void
|
|
205
|
+
beforeRelease?: (moved: boolean) => void
|
|
206
|
+
}
|
|
207
|
+
const adapters = new WeakMap<WebDrag, DragAdapter>()
|
|
208
|
+
/** Internal adapter hook: offset an active contact without moving it under the pointer. */
|
|
209
|
+
export function shiftDrag(drag: WebDrag, delta: DragPoint) {
|
|
210
|
+
adapters.get(drag)?.shift(delta)
|
|
211
|
+
}
|
|
212
|
+
/** Internal adapter hook: inspect the final input before release motion changes values. */
|
|
213
|
+
export function beforeDragRelease(drag: WebDrag, callback: (moved: boolean) => void) {
|
|
214
|
+
const adapter = adapters.get(drag)
|
|
215
|
+
if (adapter) adapter.beforeRelease = callback
|
|
216
|
+
}
|
|
217
|
+
const contains = (target: HTMLElement, handle: HTMLElement) => {
|
|
218
|
+
for (const node of renderedAncestors(handle))
|
|
219
|
+
if (node === target) return handle !== target
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
|
|
200
223
|
export function createDrag(target: HTMLElement, input: WebDragOptions, host: DragHost) {
|
|
201
224
|
const options = dragOptions(input)
|
|
202
225
|
const document = target.ownerDocument
|
|
203
226
|
const view = document.defaultView! as Window & typeof globalThis
|
|
227
|
+
const handle = options.handle ?? target
|
|
228
|
+
if (
|
|
229
|
+
options.handle !== undefined &&
|
|
230
|
+
(!(handle instanceof view.HTMLElement) ||
|
|
231
|
+
handle.ownerDocument !== document ||
|
|
232
|
+
!contains(target, handle))
|
|
233
|
+
)
|
|
234
|
+
throw new TypeError('A drag handle must be an element inside the dragged element.')
|
|
204
235
|
const abort = new view.AbortController()
|
|
205
|
-
const originalTouch =
|
|
236
|
+
const originalTouch = handle.style.touchAction
|
|
206
237
|
const touch = options.axis === 'x' ? 'pan-y' : options.axis === 'y' ? 'pan-x' : 'none'
|
|
207
|
-
|
|
238
|
+
handle.style.touchAction = touch
|
|
208
239
|
let pointer: number | undefined,
|
|
209
240
|
disabled = options.disabled ?? false,
|
|
210
241
|
disposed = false,
|
|
@@ -251,10 +282,11 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
251
282
|
disposed ||
|
|
252
283
|
!target.isConnected ||
|
|
253
284
|
target.ownerDocument !== document ||
|
|
254
|
-
target.matches(':disabled')
|
|
285
|
+
target.matches(':disabled') ||
|
|
286
|
+
(handle !== target && (handle.matches(':disabled') || !contains(target, handle)))
|
|
255
287
|
)
|
|
256
288
|
return true
|
|
257
|
-
for (const node of renderedAncestors(
|
|
289
|
+
for (const node of renderedAncestors(handle)) {
|
|
258
290
|
if (
|
|
259
291
|
node.hasAttribute('inert') ||
|
|
260
292
|
node.getAttribute('aria-disabled') === 'true' ||
|
|
@@ -285,7 +317,7 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
285
317
|
notify('onMove', velocity(samples.at(-1)?.time ?? 0))
|
|
286
318
|
}
|
|
287
319
|
const releaseCapture = (id: number) => {
|
|
288
|
-
if (
|
|
320
|
+
if (handle.hasPointerCapture(id)) handle.releasePointerCapture(id)
|
|
289
321
|
}
|
|
290
322
|
const stopRelease = () => {
|
|
291
323
|
const current = animation
|
|
@@ -363,7 +395,7 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
363
395
|
passive,
|
|
364
396
|
capture: captureEvent,
|
|
365
397
|
})
|
|
366
|
-
listen(
|
|
398
|
+
listen(handle, 'pointerdown', (event: PointerEvent) => {
|
|
367
399
|
if (
|
|
368
400
|
disposed ||
|
|
369
401
|
pointer !== undefined ||
|
|
@@ -374,7 +406,7 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
374
406
|
)
|
|
375
407
|
return
|
|
376
408
|
for (const node of event.composedPath()) {
|
|
377
|
-
if (node ===
|
|
409
|
+
if (node === handle) break
|
|
378
410
|
if (
|
|
379
411
|
node instanceof view.Element &&
|
|
380
412
|
node.matches(
|
|
@@ -404,7 +436,7 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
404
436
|
samples = []
|
|
405
437
|
capture(event)
|
|
406
438
|
try {
|
|
407
|
-
|
|
439
|
+
handle.setPointerCapture(pointer)
|
|
408
440
|
notify('onStart')
|
|
409
441
|
} catch (error) {
|
|
410
442
|
cancel()
|
|
@@ -436,6 +468,15 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
436
468
|
capture(event)
|
|
437
469
|
flush()
|
|
438
470
|
if (pointer === undefined) return
|
|
471
|
+
const hook = adapter.beforeRelease
|
|
472
|
+
if (hook) {
|
|
473
|
+
try {
|
|
474
|
+
hook(moved)
|
|
475
|
+
} catch (error) {
|
|
476
|
+
host.error(error)
|
|
477
|
+
}
|
|
478
|
+
if (pointer === undefined) return
|
|
479
|
+
}
|
|
439
480
|
const p = position(),
|
|
440
481
|
id = pointer
|
|
441
482
|
pointer = undefined
|
|
@@ -494,7 +535,7 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
494
535
|
}
|
|
495
536
|
watchRoot()
|
|
496
537
|
listen(
|
|
497
|
-
|
|
538
|
+
handle,
|
|
498
539
|
'dragstart',
|
|
499
540
|
(event) => {
|
|
500
541
|
if (pointer !== undefined) event.preventDefault()
|
|
@@ -502,7 +543,7 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
502
543
|
false,
|
|
503
544
|
)
|
|
504
545
|
listen(
|
|
505
|
-
|
|
546
|
+
handle,
|
|
506
547
|
'click',
|
|
507
548
|
(event: MouseEvent) => {
|
|
508
549
|
if (event.detail > 0 && view.performance.now() < suppressUntil) {
|
|
@@ -525,10 +566,25 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
525
566
|
scrollRoot = undefined
|
|
526
567
|
if (stopAnimation && target.isConnected && target.ownerDocument === document)
|
|
527
568
|
animation?.stop()
|
|
528
|
-
if (
|
|
569
|
+
if (handle.style.touchAction === touch) handle.style.touchAction = originalTouch
|
|
529
570
|
host.dispose()
|
|
530
571
|
}
|
|
531
|
-
|
|
572
|
+
const adapter: DragAdapter = {
|
|
573
|
+
shift: (delta) => shift(delta),
|
|
574
|
+
}
|
|
575
|
+
const shift = (delta: DragPoint) => {
|
|
576
|
+
if (pointer === undefined || disposed) return
|
|
577
|
+
const before = position()
|
|
578
|
+
origin = { x: origin.x + delta.x, y: origin.y + delta.y }
|
|
579
|
+
const after = position()
|
|
580
|
+
samples = samples.map((sample) => ({
|
|
581
|
+
...sample,
|
|
582
|
+
x: sample.x + after.x - before.x,
|
|
583
|
+
y: sample.y + after.y - before.y,
|
|
584
|
+
}))
|
|
585
|
+
host.write(after, velocity(samples.at(-1)?.time ?? 0))
|
|
586
|
+
}
|
|
587
|
+
const controller = {
|
|
532
588
|
handle: {
|
|
533
589
|
get dragging() {
|
|
534
590
|
return pointer !== undefined
|
|
@@ -580,4 +636,6 @@ export function createDrag(target: HTMLElement, input: WebDragOptions, host: Dra
|
|
|
580
636
|
},
|
|
581
637
|
detach: () => dispose(!!options.value),
|
|
582
638
|
}
|
|
639
|
+
adapters.set(controller.handle, adapter)
|
|
640
|
+
return controller
|
|
583
641
|
}
|
package/src/web/drop-zones.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { createDropPolicy, type DropOptions, type DropZones } from '../core/drop.js'
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
beforeDragRelease,
|
|
4
|
+
shiftDrag,
|
|
5
|
+
type WebDrag,
|
|
6
|
+
type WebDragOptions,
|
|
7
|
+
} from './drag.js'
|
|
3
8
|
import { inputBlocked, inputEvents, type InteractionHost } from './interaction.js'
|
|
4
|
-
export type {
|
|
9
|
+
export type {
|
|
10
|
+
DropEvent,
|
|
11
|
+
DropMoveEvent,
|
|
12
|
+
DropPadding,
|
|
13
|
+
DropRect,
|
|
14
|
+
DropTarget,
|
|
15
|
+
DropZones,
|
|
16
|
+
DropEffect,
|
|
17
|
+
} from '../core/drop.js'
|
|
5
18
|
export interface WebDropZoneOptions extends DropOptions<HTMLElement> {
|
|
6
19
|
drag?: Omit<
|
|
7
20
|
WebDragOptions,
|
|
@@ -46,6 +59,12 @@ export function createDropZones(
|
|
|
46
59
|
enabled: () => !document.hidden && !inputOptions.disabled,
|
|
47
60
|
cancelDrag: cancelInput,
|
|
48
61
|
error: host.error,
|
|
62
|
+
translation: () => input?.position ?? { x: 0, y: 0 },
|
|
63
|
+
shift: (x, y) => {
|
|
64
|
+
if (!input) return
|
|
65
|
+
shiftDrag(input, { x, y })
|
|
66
|
+
flushSource()
|
|
67
|
+
},
|
|
49
68
|
})
|
|
50
69
|
const releases: (() => void)[] = []
|
|
51
70
|
const controller: DropZones = {
|
|
@@ -65,7 +84,11 @@ export function createDropZones(
|
|
|
65
84
|
return policy.handle.disposed
|
|
66
85
|
},
|
|
67
86
|
request: (id) => policy.handle.request(id),
|
|
68
|
-
refresh: () =>
|
|
87
|
+
refresh: () => {
|
|
88
|
+
// Measure the painted translation, not a newer unrendered input sample.
|
|
89
|
+
if (policy.handle.dragging) flushSource()
|
|
90
|
+
policy.handle.refresh()
|
|
91
|
+
},
|
|
69
92
|
cancel() {
|
|
70
93
|
try {
|
|
71
94
|
policy.handle.cancel()
|
|
@@ -104,13 +127,15 @@ export function createDropZones(
|
|
|
104
127
|
flushSource()
|
|
105
128
|
policy.move()
|
|
106
129
|
},
|
|
107
|
-
onRelease: () => {
|
|
108
|
-
policy.release()
|
|
109
|
-
},
|
|
110
130
|
onCancel: () => {
|
|
111
131
|
if (!handoff) policy.handle.cancel()
|
|
112
132
|
},
|
|
113
133
|
})
|
|
134
|
+
// Commit before the return motion starts; reduced motion would settle it at once.
|
|
135
|
+
beforeDragRelease(input, (moved) => {
|
|
136
|
+
if (moved) policy.release()
|
|
137
|
+
else policy.handle.cancel()
|
|
138
|
+
})
|
|
114
139
|
releases.push(host.register(source, controller, controller.refresh))
|
|
115
140
|
// A removed accepting target cancels its owned effect.
|
|
116
141
|
for (const entry of options.targets)
|
package/src/web/loading.ts
CHANGED
|
@@ -8,13 +8,14 @@ import {
|
|
|
8
8
|
type EffectHost,
|
|
9
9
|
} from './effect.js'
|
|
10
10
|
import { parseColor } from './style.js'
|
|
11
|
+
import { loaderDefinition, shimmerDefinition, type LoaderStyle } from '../core/loading.js'
|
|
11
12
|
import type { WebInteraction } from './interaction.js'
|
|
12
13
|
|
|
13
14
|
export interface WebShimmerOptions {
|
|
14
15
|
duration?: number
|
|
15
16
|
}
|
|
16
17
|
export interface WebLoaderOptions {
|
|
17
|
-
style?:
|
|
18
|
+
style?: LoaderStyle
|
|
18
19
|
color?: string
|
|
19
20
|
size?: number
|
|
20
21
|
duration?: number
|
|
@@ -35,17 +36,26 @@ export function createShimmer(
|
|
|
35
36
|
effectTarget(target, host)
|
|
36
37
|
effectOptions(options, ['duration'])
|
|
37
38
|
const duration =
|
|
38
|
-
finiteOption(
|
|
39
|
-
|
|
39
|
+
finiteOption(
|
|
40
|
+
options.duration ?? shimmerDefinition.duration,
|
|
41
|
+
'Shimmer duration',
|
|
42
|
+
0,
|
|
43
|
+
false,
|
|
44
|
+
) / host.settings().timeScale
|
|
45
|
+
const { angle, stops, opacity, travel } = shimmerDefinition.highlight
|
|
46
|
+
const band = stops
|
|
47
|
+
.map((stop, i) =>
|
|
48
|
+
opacity[i] ? `rgba(255,255,255,${opacity[i]}) ${stop}%` : `transparent ${stop}%`,
|
|
49
|
+
)
|
|
50
|
+
.join(',')
|
|
40
51
|
return loading(target, 'shimmer', host, (layer) => {
|
|
41
52
|
const beam = host.document.createElement('span')
|
|
42
|
-
beam.style.cssText =
|
|
43
|
-
'display:block;position:absolute;inset:0;background:linear-gradient(100deg,transparent 15%,rgba(255,255,255,.55) 50%,transparent 85%);'
|
|
53
|
+
beam.style.cssText = `display:block;position:absolute;inset:0;background:linear-gradient(${angle}deg,${band});`
|
|
44
54
|
layer.append(beam)
|
|
45
55
|
return [
|
|
46
56
|
{
|
|
47
57
|
target: beam,
|
|
48
|
-
frames:
|
|
58
|
+
frames: travel.map((x) => ({ transform: `translateX(${x * 100}%)` })),
|
|
49
59
|
timing: { duration },
|
|
50
60
|
},
|
|
51
61
|
]
|
|
@@ -59,17 +69,27 @@ export function createLoader(
|
|
|
59
69
|
) {
|
|
60
70
|
effectTarget(target, host)
|
|
61
71
|
effectOptions(options, ['style', 'color', 'size', 'duration'])
|
|
62
|
-
const style = options.style ??
|
|
63
|
-
color = options.color ??
|
|
72
|
+
const style = options.style ?? loaderDefinition.style,
|
|
73
|
+
color = options.color ?? loaderDefinition.color
|
|
64
74
|
if (!['dots', 'bars', 'ring'].includes(style))
|
|
65
75
|
throw new TypeError('Unknown loader style.')
|
|
66
76
|
if (typeof color !== 'string')
|
|
67
77
|
throw new TypeError('Loader color must be a resolved CSS color.')
|
|
68
78
|
parseColor(host.document, color)
|
|
69
|
-
const size = finiteOption(
|
|
79
|
+
const size = finiteOption(
|
|
80
|
+
options.size ?? loaderDefinition.size,
|
|
81
|
+
'Loader size',
|
|
82
|
+
0,
|
|
83
|
+
false,
|
|
84
|
+
)
|
|
70
85
|
const duration =
|
|
71
|
-
finiteOption(
|
|
72
|
-
|
|
86
|
+
finiteOption(
|
|
87
|
+
options.duration ?? loaderDefinition.duration,
|
|
88
|
+
'Loader duration',
|
|
89
|
+
0,
|
|
90
|
+
false,
|
|
91
|
+
) / host.settings().timeScale
|
|
92
|
+
const css = loaderDefinition.css
|
|
73
93
|
return loading(target, 'loader', host, (layer) => {
|
|
74
94
|
layer.style.display = 'grid'
|
|
75
95
|
layer.style.placeItems = 'center'
|
|
@@ -78,7 +98,7 @@ export function createLoader(
|
|
|
78
98
|
layer.append(box)
|
|
79
99
|
if (style === 'ring') {
|
|
80
100
|
const ring = host.document.createElement('span')
|
|
81
|
-
ring.style.cssText = `display:block;width
|
|
101
|
+
ring.style.cssText = `display:block;width:${css.ring.diameter}%;aspect-ratio:1;box-sizing:border-box;border:${css.ring.border}px solid ${color};border-right-color:transparent;border-radius:50%;`
|
|
82
102
|
box.append(ring)
|
|
83
103
|
return [
|
|
84
104
|
{
|
|
@@ -88,18 +108,19 @@ export function createLoader(
|
|
|
88
108
|
},
|
|
89
109
|
]
|
|
90
110
|
}
|
|
91
|
-
|
|
111
|
+
const shape = css[style]
|
|
112
|
+
return Array.from({ length: loaderDefinition.kinds[style].count }, (_, i) => {
|
|
92
113
|
const item = host.document.createElement('span')
|
|
93
|
-
item.style.cssText = `display:block;background:${color};width:${
|
|
114
|
+
item.style.cssText = `display:block;background:${color};width:${shape.width}%;height:${shape.height}%;border-radius:${shape.radius};`
|
|
94
115
|
box.append(item)
|
|
95
116
|
return {
|
|
96
117
|
target: item,
|
|
97
118
|
frames: [
|
|
98
|
-
{ transform:
|
|
119
|
+
{ transform: `scaleY(${css.pulse})`, opacity: css.pulse },
|
|
99
120
|
{ transform: 'scaleY(1)', opacity: 1 },
|
|
100
|
-
{ transform:
|
|
121
|
+
{ transform: `scaleY(${css.pulse})`, opacity: css.pulse },
|
|
101
122
|
],
|
|
102
|
-
timing: { duration, delay: (i * duration) /
|
|
123
|
+
timing: { duration, delay: (i * duration) / css.stagger, easing: css.ease },
|
|
103
124
|
}
|
|
104
125
|
})
|
|
105
126
|
})
|
package/src/web/pointer.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface WebPointerFollow {
|
|
|
20
20
|
readonly y: WebReadableValue
|
|
21
21
|
readonly presence: WebReadableValue
|
|
22
22
|
readonly active: boolean
|
|
23
|
+
/** Pointer Events plus `(any-hover: hover)` while the source is listening; not a connected-device check. */
|
|
24
|
+
readonly available: boolean
|
|
23
25
|
readonly disposed: boolean
|
|
24
26
|
readonly suspended: boolean
|
|
25
27
|
update(state: { disabled: boolean }): void
|
|
@@ -110,6 +112,10 @@ export function createPointerFollow(
|
|
|
110
112
|
active = false
|
|
111
113
|
let dirty = false,
|
|
112
114
|
latest: { x: number; y: number; id: number } | undefined
|
|
115
|
+
const hover =
|
|
116
|
+
typeof view.PointerEvent === 'function'
|
|
117
|
+
? view.matchMedia?.('(any-hover: hover)')
|
|
118
|
+
: undefined
|
|
113
119
|
let abort: AbortController | undefined
|
|
114
120
|
let observer: MutationObserver | undefined
|
|
115
121
|
let resize: ResizeObserver | undefined
|
|
@@ -204,6 +210,10 @@ export function createPointerFollow(
|
|
|
204
210
|
if (document.hidden) reset(true)
|
|
205
211
|
else invalidate()
|
|
206
212
|
}
|
|
213
|
+
const capability = () => {
|
|
214
|
+
if (!hover?.matches) reset()
|
|
215
|
+
else invalidate()
|
|
216
|
+
}
|
|
207
217
|
const detach = () => {
|
|
208
218
|
abort?.abort()
|
|
209
219
|
abort = undefined
|
|
@@ -222,6 +232,7 @@ export function createPointerFollow(
|
|
|
222
232
|
region.addEventListener('pointercancel', leave, options)
|
|
223
233
|
view.addEventListener('blur', () => reset(true), options)
|
|
224
234
|
document.addEventListener('visibilitychange', visibility, options)
|
|
235
|
+
hover?.addEventListener?.('change', capability, options)
|
|
225
236
|
document.addEventListener('scroll', invalidate, { ...options, capture: true })
|
|
226
237
|
view.addEventListener('resize', invalidate, options)
|
|
227
238
|
document.fonts?.addEventListener('loadingdone', invalidate, options)
|
|
@@ -260,6 +271,9 @@ export function createPointerFollow(
|
|
|
260
271
|
get active() {
|
|
261
272
|
return active
|
|
262
273
|
},
|
|
274
|
+
get available() {
|
|
275
|
+
return !disposed && !suspended && !!hover?.matches && !blocked()
|
|
276
|
+
},
|
|
263
277
|
get disposed() {
|
|
264
278
|
return disposed
|
|
265
279
|
},
|
package/src/web/public.ts
CHANGED
package/src/web/reveal.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createTrack } from '../core/track.js'
|
|
2
2
|
import { resolveTransition } from '../core/transition.js'
|
|
3
|
+
import { revealDefinition, revealRadius } from '../core/loading.js'
|
|
3
4
|
import { flatten, sampled } from './curve.js'
|
|
4
5
|
import { borderBox } from './style.js'
|
|
5
6
|
import {
|
|
@@ -31,7 +32,10 @@ export function createReveal(
|
|
|
31
32
|
) {
|
|
32
33
|
const view = effectTarget(target, host)
|
|
33
34
|
effectOptions(options, [opening ? 'from' : 'to', 'duration'])
|
|
34
|
-
const duration = finiteOption(
|
|
35
|
+
const duration = finiteOption(
|
|
36
|
+
options.duration ?? revealDefinition.duration,
|
|
37
|
+
'Reveal duration',
|
|
38
|
+
)
|
|
35
39
|
const point = opening
|
|
36
40
|
? (options as WebRevealOptions).from
|
|
37
41
|
: (options as WebConcealOptions).to
|
|
@@ -46,18 +50,13 @@ export function createReveal(
|
|
|
46
50
|
const bounds = borderBox(target),
|
|
47
51
|
x = point?.x ?? bounds.width / 2,
|
|
48
52
|
y = point?.y ?? bounds.height / 2
|
|
49
|
-
const radius =
|
|
50
|
-
Math.hypot(x, y),
|
|
51
|
-
Math.hypot(bounds.width - x, y),
|
|
52
|
-
Math.hypot(x, bounds.height - y),
|
|
53
|
-
Math.hypot(bounds.width - x, bounds.height - y),
|
|
54
|
-
)
|
|
53
|
+
const radius = revealRadius(bounds.width, bounds.height, x, y)
|
|
55
54
|
const initial = before?.circle() ?? { x, y, radius: opening ? 0 : radius }
|
|
56
55
|
const destination = { x, y, radius: opening ? radius : 0 }
|
|
57
56
|
const track = createTrack(
|
|
58
57
|
'x',
|
|
59
58
|
[0, 1],
|
|
60
|
-
resolveTransition({ duration, ease:
|
|
59
|
+
resolveTransition({ duration, ease: revealDefinition.ease }, 'x', 2),
|
|
61
60
|
0,
|
|
62
61
|
host.settings(),
|
|
63
62
|
)
|
package/src/web/text-effect.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { graphemes, diffFromEnd } from '../core/roll-diff.js'
|
|
2
|
+
import {
|
|
3
|
+
MAX_TEXT_GRAPHEMES,
|
|
4
|
+
isVisiblePiece,
|
|
5
|
+
staggeredStart,
|
|
6
|
+
textPieces as pieces,
|
|
7
|
+
type TextPiece as Piece,
|
|
8
|
+
} from '../core/text-pieces.js'
|
|
2
9
|
import { createTrack, type Track } from '../core/track.js'
|
|
3
10
|
import { resolveTransition } from '../core/transition.js'
|
|
4
11
|
import { flatten } from './curve.js'
|
|
@@ -31,7 +38,6 @@ export interface WebRollOptions {
|
|
|
31
38
|
direction?: 'auto' | 'up' | 'down'
|
|
32
39
|
tabular?: boolean
|
|
33
40
|
}
|
|
34
|
-
type Piece = { start: number; end: number; text: string; group: number }
|
|
35
41
|
type Fragment = { element: HTMLElement; start: number; text: string; group: number }
|
|
36
42
|
type Copy = { layer: HTMLElement; fragments: Fragment[]; width: number; height: number }
|
|
37
43
|
type Active = { host: EffectHost; animation: EffectAnimation; snapshot(): HTMLElement }
|
|
@@ -115,22 +121,11 @@ function source(target: HTMLElement, host: EffectHost) {
|
|
|
115
121
|
if (old && old.host.identity !== host.identity)
|
|
116
122
|
throw new Error('Text belongs to another motion context.')
|
|
117
123
|
const text = (nodes[0] as Text | undefined)?.data ?? ''
|
|
118
|
-
if (graphemes(text).length >
|
|
124
|
+
if (graphemes(text).length > MAX_TEXT_GRAPHEMES)
|
|
119
125
|
throw new RangeError('Text effects support at most 256 graphemes.')
|
|
120
126
|
return { view, css, node: nodes[0] as Text | undefined, text, old }
|
|
121
127
|
}
|
|
122
128
|
|
|
123
|
-
function pieces(text: string, by: 'letter' | 'word'): Piece[] {
|
|
124
|
-
if (by === 'letter') return graphemes(text).map((part, group) => ({ ...part, group }))
|
|
125
|
-
const segmenter = new Intl.Segmenter(undefined, { granularity: 'word' })
|
|
126
|
-
return [...segmenter.segment(text)].map(({ segment, index }, group) => ({
|
|
127
|
-
text: segment,
|
|
128
|
-
start: index,
|
|
129
|
-
end: index + segment.length,
|
|
130
|
-
group,
|
|
131
|
-
}))
|
|
132
|
-
}
|
|
133
|
-
|
|
134
129
|
/** Each piece clips a complete shaped copy, preserving ligatures, bidi and line wrapping. */
|
|
135
130
|
function copy(
|
|
136
131
|
target: HTMLElement,
|
|
@@ -156,7 +151,7 @@ function copy(
|
|
|
156
151
|
if (!node) return { layer, fragments, width, height }
|
|
157
152
|
const range = host.document.createRange()
|
|
158
153
|
for (const piece of parts) {
|
|
159
|
-
if (!piece
|
|
154
|
+
if (!isVisiblePiece(piece)) continue
|
|
160
155
|
range.setStart(node, piece.start)
|
|
161
156
|
range.setEnd(node, piece.end)
|
|
162
157
|
for (const rect of range.getClientRects()) {
|
|
@@ -321,7 +316,11 @@ export function createTextAnimation(
|
|
|
321
316
|
'x',
|
|
322
317
|
[0, 1],
|
|
323
318
|
resolveTransition(
|
|
324
|
-
{
|
|
319
|
+
{
|
|
320
|
+
duration,
|
|
321
|
+
delay: staggeredStart(fragment.group, stagger, delay),
|
|
322
|
+
ease: 'easeOut',
|
|
323
|
+
},
|
|
325
324
|
'x',
|
|
326
325
|
2,
|
|
327
326
|
),
|
|
@@ -375,7 +374,7 @@ export function createRoll(
|
|
|
375
374
|
)
|
|
376
375
|
throw new TypeError('Roll value must be finite or a string.')
|
|
377
376
|
const text = typeof value === 'string' ? value : (options.format ?? String)(value)
|
|
378
|
-
if (typeof text !== 'string' || graphemes(text).length >
|
|
377
|
+
if (typeof text !== 'string' || graphemes(text).length > MAX_TEXT_GRAPHEMES)
|
|
379
378
|
throw new RangeError('Roll formatting must produce at most 256 graphemes.')
|
|
380
379
|
const initial = source(target, host)
|
|
381
380
|
const previous = lastValues.get(target) ?? Number(initial.text)
|
|
@@ -419,7 +418,11 @@ export function createRoll(
|
|
|
419
418
|
createTrack(
|
|
420
419
|
'x',
|
|
421
420
|
[0, 1],
|
|
422
|
-
resolveTransition(
|
|
421
|
+
resolveTransition(
|
|
422
|
+
{ duration, bounce, delay: staggeredStart(index, stagger, delay) },
|
|
423
|
+
'x',
|
|
424
|
+
2,
|
|
425
|
+
),
|
|
423
426
|
0,
|
|
424
427
|
host.settings(),
|
|
425
428
|
)
|