@rafaelaugusto/jolt 0.1.0-beta.1 → 0.1.0-beta.11
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 +385 -18
- package/dist/animate.d.ts +19 -3
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +186 -14
- package/dist/animate.js.map +1 -1
- package/dist/color.d.ts +11 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +73 -0
- package/dist/color.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/decay.d.ts +11 -0
- package/dist/decay.d.ts.map +1 -0
- package/dist/decay.js +22 -0
- package/dist/decay.js.map +1 -0
- package/dist/decoration.d.ts +18 -0
- package/dist/decoration.d.ts.map +1 -0
- package/dist/decoration.js +155 -0
- package/dist/decoration.js.map +1 -0
- package/dist/drag-input.d.ts +15 -0
- package/dist/drag-input.d.ts.map +1 -0
- package/dist/drag-input.js +66 -0
- package/dist/drag-input.js.map +1 -0
- package/dist/drag-physics.d.ts +20 -0
- package/dist/drag-physics.d.ts.map +1 -0
- package/dist/drag-physics.js +75 -0
- package/dist/drag-physics.js.map +1 -0
- package/dist/drag.d.ts +58 -0
- package/dist/drag.d.ts.map +1 -0
- package/dist/drag.js +324 -0
- package/dist/drag.js.map +1 -0
- package/dist/driver/android-color.d.ts +20 -0
- package/dist/driver/android-color.d.ts.map +1 -0
- package/dist/driver/android-color.js +136 -0
- package/dist/driver/android-color.js.map +1 -0
- package/dist/driver/android-decoration.d.ts +22 -0
- package/dist/driver/android-decoration.d.ts.map +1 -0
- package/dist/driver/android-decoration.js +139 -0
- package/dist/driver/android-decoration.js.map +1 -0
- package/dist/driver/android-gradient.d.ts +5 -0
- package/dist/driver/android-gradient.d.ts.map +1 -0
- package/dist/driver/android-gradient.js +89 -0
- package/dist/driver/android-gradient.js.map +1 -0
- package/dist/driver/android-playback.d.ts +13 -0
- package/dist/driver/android-playback.d.ts.map +1 -0
- package/dist/driver/android-playback.js +141 -0
- package/dist/driver/android-playback.js.map +1 -0
- package/dist/driver/android.d.ts +1 -1
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +165 -13
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/index.d.ts +25 -2
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios-color.d.ts +16 -0
- package/dist/driver/ios-color.d.ts.map +1 -0
- package/dist/driver/ios-color.js +392 -0
- package/dist/driver/ios-color.js.map +1 -0
- package/dist/driver/ios-decoration.d.ts +17 -0
- package/dist/driver/ios-decoration.d.ts.map +1 -0
- package/dist/driver/ios-decoration.js +271 -0
- package/dist/driver/ios-decoration.js.map +1 -0
- package/dist/driver/ios-gradient.d.ts +5 -0
- package/dist/driver/ios-gradient.d.ts.map +1 -0
- package/dist/driver/ios-gradient.js +109 -0
- package/dist/driver/ios-gradient.js.map +1 -0
- package/dist/driver/ios-layout.d.ts +9 -0
- package/dist/driver/ios-layout.d.ts.map +1 -0
- package/dist/driver/ios-layout.js +52 -0
- package/dist/driver/ios-layout.js.map +1 -0
- package/dist/driver/ios-playback.d.ts +10 -0
- package/dist/driver/ios-playback.d.ts.map +1 -0
- package/dist/driver/ios-playback.js +47 -0
- package/dist/driver/ios-playback.js.map +1 -0
- package/dist/driver/ios-shadow.d.ts +6 -0
- package/dist/driver/ios-shadow.d.ts.map +1 -0
- package/dist/driver/ios-shadow.js +52 -0
- package/dist/driver/ios-shadow.js.map +1 -0
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +163 -28
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +75 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +293 -23
- package/dist/engine.js.map +1 -1
- package/dist/gradient.d.ts +90 -0
- package/dist/gradient.d.ts.map +1 -0
- package/dist/gradient.js +247 -0
- package/dist/gradient.js.map +1 -0
- package/dist/haptics.d.ts +21 -0
- package/dist/haptics.d.ts.map +1 -0
- package/dist/haptics.js +120 -0
- package/dist/haptics.js.map +1 -0
- package/dist/image-effect-native.d.ts +26 -0
- package/dist/image-effect-native.d.ts.map +1 -0
- package/dist/image-effect-native.js +73 -0
- package/dist/image-effect-native.js.map +1 -0
- package/dist/image-effect.d.ts +50 -0
- package/dist/image-effect.d.ts.map +1 -0
- package/dist/image-effect.js +290 -0
- package/dist/image-effect.js.map +1 -0
- package/dist/image-owner.d.ts +3 -0
- package/dist/image-owner.d.ts.map +1 -0
- package/dist/image-owner.js +11 -0
- package/dist/image-owner.js.map +1 -0
- package/dist/index.d.ts +47 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/interaction-surface.d.ts +12 -0
- package/dist/interaction-surface.d.ts.map +1 -0
- package/dist/interaction-surface.js +36 -0
- package/dist/interaction-surface.js.map +1 -0
- package/dist/items.d.ts +20 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +109 -0
- package/dist/items.js.map +1 -0
- package/dist/keyboard.d.ts +41 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +256 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.d.ts.map +1 -0
- package/dist/label.js +60 -0
- package/dist/label.js.map +1 -0
- package/dist/layout.d.ts +13 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +348 -0
- package/dist/layout.js.map +1 -0
- package/dist/liquid-grid.d.ts +3 -0
- package/dist/liquid-grid.d.ts.map +1 -0
- package/dist/liquid-grid.js +24 -0
- package/dist/liquid-grid.js.map +1 -0
- package/dist/liquid.d.ts +18 -0
- package/dist/liquid.d.ts.map +1 -0
- package/dist/liquid.js +234 -0
- package/dist/liquid.js.map +1 -0
- package/dist/loader.d.ts +19 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +173 -0
- package/dist/loader.js.map +1 -0
- package/dist/motion-scope.d.ts +39 -0
- package/dist/motion-scope.d.ts.map +1 -0
- package/dist/motion-scope.js +238 -0
- package/dist/motion-scope.js.map +1 -0
- package/dist/motion-states.d.ts +43 -0
- package/dist/motion-states.d.ts.map +1 -0
- package/dist/motion-states.js +220 -0
- package/dist/motion-states.js.map +1 -0
- package/dist/particles.d.ts +39 -0
- package/dist/particles.d.ts.map +1 -0
- package/dist/particles.js +265 -0
- package/dist/particles.js.map +1 -0
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +90 -0
- package/dist/path.js.map +1 -0
- package/dist/playback-group.d.ts +6 -0
- package/dist/playback-group.d.ts.map +1 -0
- package/dist/playback-group.js +141 -0
- package/dist/playback-group.js.map +1 -0
- package/dist/playback.d.ts +25 -0
- package/dist/playback.d.ts.map +1 -0
- package/dist/playback.js +35 -0
- package/dist/playback.js.map +1 -0
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +23 -7
- package/dist/press.js.map +1 -1
- package/dist/properties.d.ts +6 -3
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +26 -2
- package/dist/properties.js.map +1 -1
- package/dist/reveal.d.ts +25 -0
- package/dist/reveal.d.ts.map +1 -0
- package/dist/reveal.js +294 -0
- package/dist/reveal.js.map +1 -0
- package/dist/roll-diff.d.ts +22 -0
- package/dist/roll-diff.d.ts.map +1 -0
- package/dist/roll-diff.js +36 -0
- package/dist/roll-diff.js.map +1 -0
- package/dist/roll.d.ts +29 -0
- package/dist/roll.d.ts.map +1 -0
- package/dist/roll.js +442 -0
- package/dist/roll.js.map +1 -0
- package/dist/scroll-linked.d.ts +10 -0
- package/dist/scroll-linked.d.ts.map +1 -0
- package/dist/scroll-linked.js +58 -0
- package/dist/scroll-linked.js.map +1 -0
- package/dist/sequence.d.ts +10 -5
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +208 -14
- package/dist/sequence.js.map +1 -1
- package/dist/shimmer.d.ts +12 -0
- package/dist/shimmer.d.ts.map +1 -0
- package/dist/shimmer.js +161 -0
- package/dist/shimmer.js.map +1 -0
- package/dist/svg-path.d.ts +14 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg-path.js +281 -0
- package/dist/svg-path.js.map +1 -0
- package/dist/symbol-effect.d.ts +23 -0
- package/dist/symbol-effect.d.ts.map +1 -0
- package/dist/symbol-effect.js +187 -0
- package/dist/symbol-effect.js.map +1 -0
- package/dist/text-layout.d.ts +30 -0
- package/dist/text-layout.d.ts.map +1 -0
- package/dist/text-layout.js +185 -0
- package/dist/text-layout.js.map +1 -0
- package/dist/text.d.ts +17 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +263 -0
- package/dist/text.js.map +1 -0
- package/dist/track.d.ts +3 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +266 -13
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +32 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +58 -6
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +48 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +95 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +163 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/zoom-native.d.ts +30 -0
- package/dist/zoom-native.d.ts.map +1 -0
- package/dist/zoom-native.js +56 -0
- package/dist/zoom-native.js.map +1 -0
- package/dist/zoomable.d.ts +48 -0
- package/dist/zoomable.d.ts.map +1 -0
- package/dist/zoomable.js +249 -0
- package/dist/zoomable.js.map +1 -0
- package/package.json +29 -5
- package/platforms/android/include.gradle +7 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardObserver.java +97 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
- package/platforms/android/proguard-rules.pro +21 -0
- package/platforms/ios/build.xcconfig +1 -0
- package/platforms/ios/src/JoltImageEffect.h +25 -0
- package/platforms/ios/src/JoltImageEffect.mm +242 -0
- package/platforms/ios/src/JoltInteractiveAnimation.h +18 -0
- package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
- package/platforms/ios/src/JoltPhotoZoom.h +23 -0
- package/platforms/ios/src/JoltPhotoZoom.m +229 -0
- package/platforms/ios/src/module.modulemap +6 -0
- package/src/android-plugin.d.ts +184 -0
- package/src/animate.ts +249 -36
- package/src/color.ts +101 -0
- package/src/config.ts +84 -0
- package/src/decay.ts +45 -0
- package/src/decoration.ts +180 -0
- package/src/drag-input.ts +74 -0
- package/src/drag-physics.ts +110 -0
- package/src/drag.ts +395 -0
- package/src/driver/android-color.ts +170 -0
- package/src/driver/android-decoration.ts +182 -0
- package/src/driver/android-gradient.ts +109 -0
- package/src/driver/android-playback.ts +157 -0
- package/src/driver/android.ts +208 -20
- package/src/driver/index.ts +37 -2
- package/src/driver/ios-color.ts +441 -0
- package/src/driver/ios-decoration.ts +332 -0
- package/src/driver/ios-gradient.ts +114 -0
- package/src/driver/ios-layout.ts +63 -0
- package/src/driver/ios-playback.ts +51 -0
- package/src/driver/ios-shadow.ts +50 -0
- package/src/driver/ios.ts +193 -33
- package/src/engine.ts +321 -27
- package/src/gradient.ts +372 -0
- package/src/haptics.ts +148 -0
- package/src/image-effect-native.ts +129 -0
- package/src/image-effect.ts +336 -0
- package/src/image-owner.ts +12 -0
- package/src/index.ts +75 -4
- package/src/interaction-surface.ts +41 -0
- package/src/ios-plugin.d.ts +81 -0
- package/src/items.ts +140 -0
- package/src/keyboard.ts +291 -0
- package/src/label.ts +76 -0
- package/src/layout.ts +408 -0
- package/src/liquid-grid.ts +28 -0
- package/src/liquid.ts +287 -0
- package/src/loader.ts +202 -0
- package/src/motion-scope.ts +279 -0
- package/src/motion-states.ts +269 -0
- package/src/particles.ts +323 -0
- package/src/path.ts +124 -0
- package/src/playback-group.ts +143 -0
- package/src/playback.ts +62 -0
- package/src/press.ts +26 -10
- package/src/properties.ts +32 -6
- package/src/reveal.ts +374 -0
- package/src/roll-diff.ts +57 -0
- package/src/roll.ts +515 -0
- package/src/scroll-linked.ts +73 -0
- package/src/sequence.ts +204 -14
- package/src/shimmer.ts +180 -0
- package/src/svg-path.ts +355 -0
- package/src/symbol-effect.ts +226 -0
- package/src/text-layout.ts +228 -0
- package/src/text.ts +279 -0
- package/src/track.ts +347 -12
- package/src/transition.ts +93 -6
- package/src/types.ts +49 -1
- package/src/vue/index.ts +231 -0
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
package/src/engine.ts
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClock,
|
|
3
|
+
validateProgress,
|
|
4
|
+
type PlaybackAnimation,
|
|
5
|
+
type PlaybackClock,
|
|
6
|
+
type PlaybackCommand,
|
|
7
|
+
} from './playback.js'
|
|
1
8
|
import type { View } from '@nativescript/core'
|
|
2
9
|
import { getDriver } from './driver/index.js'
|
|
10
|
+
import { playHaptics, type HapticOptions } from './haptics.js'
|
|
3
11
|
import { readValue, writeValue, type Property } from './properties.js'
|
|
4
12
|
import { createTrack, type Track } from './track.js'
|
|
5
13
|
import type { ResolvedTransition } from './transition.js'
|
|
14
|
+
import { isColorProperty } from './color.js'
|
|
15
|
+
import type { PreparedPath, PathProperty } from './path.js'
|
|
6
16
|
|
|
7
17
|
export interface Animation extends PromiseLike<boolean> {
|
|
8
18
|
/**
|
|
9
19
|
* Resolves with `true` once the animation played to the end, or with `false`
|
|
10
|
-
* if it was stopped or
|
|
11
|
-
*
|
|
20
|
+
* if it was stopped, superseded or skipped.
|
|
21
|
+
* Playback cancellation never rejects; sequences can reject setup/callback errors.
|
|
12
22
|
*/
|
|
13
23
|
readonly finished: Promise<boolean>
|
|
14
|
-
/** Stops
|
|
24
|
+
/** Stops playback. Each effect documents whether it preserves or restores its visual state. */
|
|
15
25
|
stop(): void
|
|
16
26
|
}
|
|
17
27
|
|
|
@@ -21,6 +31,7 @@ export interface Plan {
|
|
|
21
31
|
keyframes: readonly (number | null)[]
|
|
22
32
|
transition: ResolvedTransition
|
|
23
33
|
relative?: boolean
|
|
34
|
+
path?: PreparedPath
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
interface Entry {
|
|
@@ -29,11 +40,32 @@ interface Entry {
|
|
|
29
40
|
readonly owner: ViewAnimation
|
|
30
41
|
handle: unknown
|
|
31
42
|
done: boolean
|
|
43
|
+
finished: boolean
|
|
44
|
+
generation: number
|
|
45
|
+
token: symbol
|
|
46
|
+
completedValue?: number
|
|
32
47
|
}
|
|
33
48
|
|
|
49
|
+
const revisions = new WeakMap<View, Map<Property, symbol>>()
|
|
50
|
+
|
|
34
51
|
const running = new WeakMap<View, Map<Property, Entry>>()
|
|
35
52
|
const arrivals = new WeakMap<Animation, Promise<unknown>>()
|
|
36
53
|
|
|
54
|
+
/** Internal forecast for starting a timeline step before another one arrives. */
|
|
55
|
+
export interface ArrivalTiming {
|
|
56
|
+
read(): { remaining: number | undefined; paused: boolean; arrived: boolean }
|
|
57
|
+
subscribe(callback: () => void): () => void
|
|
58
|
+
}
|
|
59
|
+
const arrivalTimings = new WeakMap<Animation, ArrivalTiming>()
|
|
60
|
+
|
|
61
|
+
export function arrivalTimingOf(animation: Animation) {
|
|
62
|
+
return arrivalTimings.get(animation)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function setArrivalTiming(animation: Animation, timing: ArrivalTiming) {
|
|
66
|
+
arrivalTimings.set(animation, timing)
|
|
67
|
+
}
|
|
68
|
+
|
|
37
69
|
/**
|
|
38
70
|
* Resolves once the animation looks done, which for a spring is well before
|
|
39
71
|
* `finished`. Sequences move on to the next step then.
|
|
@@ -46,7 +78,7 @@ export function setArrival(animation: Animation, arrival: Promise<unknown>) {
|
|
|
46
78
|
arrivals.set(animation, arrival)
|
|
47
79
|
}
|
|
48
80
|
|
|
49
|
-
export class ViewAnimation implements
|
|
81
|
+
export class ViewAnimation implements PlaybackAnimation {
|
|
50
82
|
readonly finished: Promise<boolean>
|
|
51
83
|
private resolve!: (finished: boolean) => void
|
|
52
84
|
private readonly entries: Entry[] = []
|
|
@@ -56,13 +88,203 @@ export class ViewAnimation implements Animation {
|
|
|
56
88
|
private cancelWait: (() => void) | undefined
|
|
57
89
|
private arrive!: () => void
|
|
58
90
|
private cancelArrival: (() => void) | undefined
|
|
91
|
+
private haptics: ReturnType<typeof playHaptics>
|
|
92
|
+
private clock: PlaybackClock | undefined
|
|
93
|
+
private _paused = false
|
|
94
|
+
private controlled = false
|
|
95
|
+
private pendingProgress: number | undefined
|
|
96
|
+
private total = 0
|
|
97
|
+
private started = false
|
|
98
|
+
private arrival = 0
|
|
99
|
+
private arrived = false
|
|
100
|
+
private idleListener: (() => void) | undefined
|
|
101
|
+
private readyListener: (() => void) | undefined
|
|
102
|
+
private readonly endless: boolean
|
|
103
|
+
private readonly timingListeners = new Set<() => void>()
|
|
104
|
+
private timingChanged() {
|
|
105
|
+
for (const callback of [...this.timingListeners]) callback()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get paused() {
|
|
109
|
+
return this._paused
|
|
110
|
+
}
|
|
111
|
+
get duration() {
|
|
112
|
+
return this.started ? this.total : this.endless ? Infinity : 0
|
|
113
|
+
}
|
|
114
|
+
get currentTime() {
|
|
115
|
+
return this.time()
|
|
116
|
+
}
|
|
117
|
+
get ready() {
|
|
118
|
+
return this.started || this.settled
|
|
119
|
+
}
|
|
120
|
+
get idle() {
|
|
121
|
+
return this.settled || (this.started && this.pending === 0)
|
|
122
|
+
}
|
|
123
|
+
get progress() {
|
|
124
|
+
if (!Number.isFinite(this.duration))
|
|
125
|
+
throw new RangeError('Progress requires a finite animation.')
|
|
126
|
+
if (this.settled && this.complete) return 1
|
|
127
|
+
return (
|
|
128
|
+
this.pendingProgress ??
|
|
129
|
+
(this.duration ? Math.min(1, this.time() / this.duration) : this.settled ? 1 : 0)
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
set progress(value: number) {
|
|
133
|
+
validateProgress(value, this.duration)
|
|
134
|
+
if (this.settled) return
|
|
135
|
+
if (!this.started) {
|
|
136
|
+
this.pendingProgress = value
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
this.seekTime(this.duration * value)
|
|
140
|
+
}
|
|
141
|
+
onIdle(callback: () => void) {
|
|
142
|
+
this.idleListener = callback
|
|
143
|
+
}
|
|
144
|
+
onReady(callback: () => void) {
|
|
145
|
+
this.readyListener = callback
|
|
146
|
+
}
|
|
147
|
+
releaseGroup() {
|
|
148
|
+
this.held = false
|
|
149
|
+
if (this.idle && !this._paused) this.settle()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
pause() {
|
|
153
|
+
if (this.settled || this._paused) return
|
|
154
|
+
this.controlled = true
|
|
155
|
+
this._paused = true
|
|
156
|
+
this.clock?.pause()
|
|
157
|
+
this.cancelArrival?.()
|
|
158
|
+
this.haptics?.pause()
|
|
159
|
+
if (this.started) this.controlTracks(this.time(), 'pause')
|
|
160
|
+
this.timingChanged()
|
|
161
|
+
}
|
|
162
|
+
play() {
|
|
163
|
+
if (this.settled || !this._paused) return
|
|
164
|
+
this.controlled = true
|
|
165
|
+
this._paused = false
|
|
166
|
+
this.clock?.play()
|
|
167
|
+
if (this.started) {
|
|
168
|
+
this.controlTracks(this.time(), 'play')
|
|
169
|
+
this.scheduleArrival()
|
|
170
|
+
this.haptics?.play()
|
|
171
|
+
if (this.pending === 0) this.becameIdle()
|
|
172
|
+
}
|
|
173
|
+
this.timingChanged()
|
|
174
|
+
}
|
|
175
|
+
seekTime(time: number) {
|
|
176
|
+
if (this.settled) return
|
|
177
|
+
this.controlled = true
|
|
178
|
+
const position = Math.max(0, Math.min(this.total, time))
|
|
179
|
+
this.clock?.seek(position)
|
|
180
|
+
if (!this._paused) this.clock?.play()
|
|
181
|
+
for (const entry of this.entries) {
|
|
182
|
+
if (
|
|
183
|
+
entry.done &&
|
|
184
|
+
entry.finished &&
|
|
185
|
+
revisions.get(this.view)?.get(entry.track.property) === entry.token &&
|
|
186
|
+
readValue(this.view, entry.track.property) === entry.completedValue
|
|
187
|
+
) {
|
|
188
|
+
entry.done = false
|
|
189
|
+
entry.finished = false
|
|
190
|
+
this.pending++
|
|
191
|
+
entriesOf(this.view).set(entry.track.property, entry)
|
|
192
|
+
this.playEntry(entry, position)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
this.controlTracks(position)
|
|
196
|
+
this.scheduleArrival()
|
|
197
|
+
this.haptics?.seek()
|
|
198
|
+
if (this.pending === 0) this.becameIdle()
|
|
199
|
+
this.timingChanged()
|
|
200
|
+
}
|
|
201
|
+
elapsed(entry: Entry, now: number) {
|
|
202
|
+
return this.controlled
|
|
203
|
+
? this.time()
|
|
204
|
+
: (getDriver().elapsed?.(entry.handle) ?? now - entry.startedAt)
|
|
205
|
+
}
|
|
206
|
+
private time() {
|
|
207
|
+
return this.clock?.time() ?? 0
|
|
208
|
+
}
|
|
209
|
+
private controlTracks(position: number, command: PlaybackCommand = 'seek') {
|
|
210
|
+
const driver = getDriver()
|
|
211
|
+
for (const entry of this.entries)
|
|
212
|
+
if (!entry.done)
|
|
213
|
+
driver.control?.(this.view, entry.handle, position, this._paused, command)
|
|
214
|
+
}
|
|
215
|
+
private scheduleArrival() {
|
|
216
|
+
this.cancelArrival?.()
|
|
217
|
+
this.cancelArrival = undefined
|
|
218
|
+
if (
|
|
219
|
+
this.pending === 0 ||
|
|
220
|
+
this.arrived ||
|
|
221
|
+
this._paused ||
|
|
222
|
+
!Number.isFinite(this.arrival) ||
|
|
223
|
+
this.total <= this.arrival
|
|
224
|
+
)
|
|
225
|
+
return
|
|
226
|
+
const remaining = this.arrival - this.time()
|
|
227
|
+
if (remaining <= 0) {
|
|
228
|
+
this.arrived = true
|
|
229
|
+
this.arrive()
|
|
230
|
+
this.timingChanged()
|
|
231
|
+
} else
|
|
232
|
+
this.cancelArrival = getDriver().after(remaining, () => {
|
|
233
|
+
this.arrived = true
|
|
234
|
+
this.arrive()
|
|
235
|
+
this.timingChanged()
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
private becameIdle() {
|
|
239
|
+
this.clock?.pause()
|
|
240
|
+
this.idleListener?.()
|
|
241
|
+
if (!this.complete || (!this.held && !this._paused)) this.settle()
|
|
242
|
+
}
|
|
243
|
+
private playEntry(entry: Entry, elapsed = 0) {
|
|
244
|
+
const initial = entry.generation === 0
|
|
245
|
+
const instance = ++entry.generation
|
|
246
|
+
const driver = getDriver()
|
|
247
|
+
entry.handle = driver.play(
|
|
248
|
+
this.view,
|
|
249
|
+
entry.track,
|
|
250
|
+
initial ? entry.startedAt : driver.now() - elapsed,
|
|
251
|
+
(finished = true) => {
|
|
252
|
+
if (entry.done || instance !== entry.generation) return
|
|
253
|
+
entry.done = true
|
|
254
|
+
entry.finished = finished
|
|
255
|
+
entry.completedValue = readValue(this.view, entry.track.property)
|
|
256
|
+
forget(entry)
|
|
257
|
+
this.ended(finished)
|
|
258
|
+
},
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
private unloaded = () => this.stop()
|
|
59
262
|
|
|
60
263
|
constructor(
|
|
61
264
|
readonly view: View,
|
|
62
265
|
plans: readonly Plan[],
|
|
266
|
+
private readonly haptic?: HapticOptions,
|
|
267
|
+
private held = false,
|
|
63
268
|
) {
|
|
269
|
+
this.endless = plans.some(
|
|
270
|
+
(plan) =>
|
|
271
|
+
!Number.isFinite(plan.transition.repeat) ||
|
|
272
|
+
(plan.transition.curve.type === 'tween' &&
|
|
273
|
+
!Number.isFinite(plan.transition.curve.duration)),
|
|
274
|
+
)
|
|
64
275
|
this.finished = new Promise((resolve) => (this.resolve = resolve))
|
|
65
276
|
setArrival(this, new Promise<void>((resolve) => (this.arrive = resolve)))
|
|
277
|
+
setArrivalTiming(this, {
|
|
278
|
+
read: () => ({
|
|
279
|
+
remaining: this.started ? Math.max(0, this.arrival - this.time()) : undefined,
|
|
280
|
+
paused: this._paused,
|
|
281
|
+
arrived: this.arrived || this.settled,
|
|
282
|
+
}),
|
|
283
|
+
subscribe: (callback) => {
|
|
284
|
+
this.timingListeners.add(callback)
|
|
285
|
+
return () => this.timingListeners.delete(callback)
|
|
286
|
+
},
|
|
287
|
+
})
|
|
66
288
|
if (view.isLoaded && view.nativeViewProtected) this.start(plans)
|
|
67
289
|
else this.waitForLoad(plans)
|
|
68
290
|
}
|
|
@@ -77,6 +299,7 @@ export class ViewAnimation implements Animation {
|
|
|
77
299
|
stop() {
|
|
78
300
|
if (this.settled) return
|
|
79
301
|
this.cancelWait?.()
|
|
302
|
+
this.haptics?.cancel()
|
|
80
303
|
const now = getDriver().now()
|
|
81
304
|
for (const entry of this.entries) {
|
|
82
305
|
if (!entry.done) release(entry, now, true)
|
|
@@ -86,32 +309,45 @@ export class ViewAnimation implements Animation {
|
|
|
86
309
|
}
|
|
87
310
|
|
|
88
311
|
ended(finished: boolean) {
|
|
89
|
-
if (!finished)
|
|
90
|
-
|
|
312
|
+
if (!finished) {
|
|
313
|
+
this.complete = false
|
|
314
|
+
this.haptics?.cancel()
|
|
315
|
+
}
|
|
316
|
+
if (--this.pending === 0) this.becameIdle()
|
|
91
317
|
}
|
|
92
318
|
|
|
93
319
|
private start(plans: readonly Plan[]) {
|
|
320
|
+
this.started = true
|
|
94
321
|
const driver = getDriver()
|
|
95
322
|
const now = driver.now()
|
|
96
323
|
const entries = entriesOf(this.view)
|
|
97
324
|
|
|
98
|
-
const tracks = plans.map(({ property, keyframes, transition, relative }) => {
|
|
325
|
+
const tracks = plans.map(({ property, keyframes, transition, relative, path }) => {
|
|
99
326
|
const previous = entries.get(property)
|
|
100
327
|
let current: number
|
|
101
328
|
let velocity = 0
|
|
102
329
|
if (previous) {
|
|
103
|
-
const elapsed = now
|
|
330
|
+
const elapsed = elapsedOf(previous, now)
|
|
104
331
|
current = driver.presented(this.view, property) ?? previous.track.valueAt(elapsed)
|
|
105
|
-
velocity = previous.track.velocityAt(elapsed)
|
|
332
|
+
velocity = previous.owner.paused ? 0 : previous.track.velocityAt(elapsed)
|
|
106
333
|
release(previous, now, false)
|
|
107
334
|
} else {
|
|
108
|
-
current =
|
|
335
|
+
current =
|
|
336
|
+
(isColorProperty(property)
|
|
337
|
+
? driver.presented(this.view, property)
|
|
338
|
+
: undefined) ?? readValue(this.view, property)
|
|
109
339
|
}
|
|
340
|
+
if (path)
|
|
341
|
+
return path.track(
|
|
342
|
+
property as PathProperty,
|
|
343
|
+
relative ? current : 0,
|
|
344
|
+
transition.delay,
|
|
345
|
+
)
|
|
110
346
|
return createTrack(
|
|
111
347
|
property,
|
|
112
348
|
keyframes.map((value) => {
|
|
113
349
|
if (value === null) return current
|
|
114
|
-
if (!relative) return value
|
|
350
|
+
if (!relative || isColorProperty(property)) return value
|
|
115
351
|
return property === 'opacity' || property.startsWith('scale')
|
|
116
352
|
? current * value
|
|
117
353
|
: current + value
|
|
@@ -122,8 +358,13 @@ export class ViewAnimation implements Animation {
|
|
|
122
358
|
})
|
|
123
359
|
|
|
124
360
|
for (const track of tracks) writeValue(this.view, track.property, track.to)
|
|
361
|
+
driver.sync?.(this.view)
|
|
125
362
|
|
|
126
363
|
for (const track of tracks) {
|
|
364
|
+
let tokens = revisions.get(this.view)
|
|
365
|
+
if (!tokens) revisions.set(this.view, (tokens = new Map()))
|
|
366
|
+
const token = Symbol(track.property)
|
|
367
|
+
tokens.set(track.property, token)
|
|
127
368
|
if (track.end === 0) continue
|
|
128
369
|
const entry: Entry = {
|
|
129
370
|
track,
|
|
@@ -131,31 +372,41 @@ export class ViewAnimation implements Animation {
|
|
|
131
372
|
owner: this,
|
|
132
373
|
handle: undefined,
|
|
133
374
|
done: false,
|
|
375
|
+
finished: false,
|
|
376
|
+
generation: 0,
|
|
377
|
+
token,
|
|
134
378
|
}
|
|
135
379
|
entries.set(track.property, entry)
|
|
136
380
|
this.entries.push(entry)
|
|
137
381
|
this.pending++
|
|
138
382
|
}
|
|
139
383
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (this.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
384
|
+
this.total = Math.max(0, ...tracks.map((track) => track.end))
|
|
385
|
+
this.arrival = Math.max(0, ...tracks.map((track) => track.arrival))
|
|
386
|
+
this.clock =
|
|
387
|
+
(this.pending ? driver.clock?.(this.total) : undefined) ??
|
|
388
|
+
createClock(() => driver.now())
|
|
389
|
+
if (this._paused) this.clock.pause()
|
|
390
|
+
this.view.on('unloaded', this.unloaded)
|
|
391
|
+
this.haptics = playHaptics(this.view, tracks, this.haptic, driver, now, () =>
|
|
392
|
+
this.time(),
|
|
393
|
+
)
|
|
394
|
+
if (this._paused) this.haptics?.pause()
|
|
395
|
+
for (const entry of this.entries) this.playEntry(entry)
|
|
396
|
+
if (this.pendingProgress !== undefined) {
|
|
397
|
+
const progress = this.pendingProgress
|
|
398
|
+
this.pendingProgress = undefined
|
|
399
|
+
this.seekTime(progress * this.total)
|
|
400
|
+
} else if (this._paused) this.controlTracks(0, 'pause')
|
|
401
|
+
this.scheduleArrival()
|
|
402
|
+
this.readyListener?.()
|
|
403
|
+
this.timingChanged()
|
|
404
|
+
if (this.pending === 0) this.becameIdle()
|
|
154
405
|
}
|
|
155
406
|
|
|
156
407
|
private waitForLoad(plans: readonly Plan[]) {
|
|
157
408
|
for (const { property, keyframes, relative } of plans) {
|
|
158
|
-
if (relative) continue
|
|
409
|
+
if (relative && !isColorProperty(property)) continue
|
|
159
410
|
const first = keyframes[0]
|
|
160
411
|
if (first !== null && first !== undefined) writeValue(this.view, property, first)
|
|
161
412
|
}
|
|
@@ -174,13 +425,51 @@ export class ViewAnimation implements Animation {
|
|
|
174
425
|
if (this.settled) return
|
|
175
426
|
this.settled = true
|
|
176
427
|
this.cancelArrival?.()
|
|
428
|
+
this.clock?.cancel()
|
|
429
|
+
this.view.off('unloaded', this.unloaded)
|
|
430
|
+
if (this.complete) this.haptics?.finish()
|
|
431
|
+
else this.haptics?.cancel()
|
|
432
|
+
this.haptics = undefined
|
|
177
433
|
this.arrive()
|
|
178
434
|
this.resolve(this.complete)
|
|
435
|
+
this.timingChanged()
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Current native presentation when available, otherwise the active curve or model. */
|
|
440
|
+
export function currentValue(view: View, property: Property): number {
|
|
441
|
+
const entry = running.get(view)?.get(property)
|
|
442
|
+
if (!entry) return readValue(view, property)
|
|
443
|
+
const driver = getDriver()
|
|
444
|
+
return (
|
|
445
|
+
driver.presented(view, property) ??
|
|
446
|
+
entry.track.valueAt(elapsedOf(entry, driver.now()))
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Internal property ownership marker; layout cleanup must preserve newer animations. */
|
|
451
|
+
export function propertyRevision(view: View, property: Property) {
|
|
452
|
+
return revisions.get(view)?.get(property)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Sample an existing owner at layout time, without stealing an animation started meanwhile. */
|
|
456
|
+
export function captureValue(view: View, property: Property): () => number | undefined {
|
|
457
|
+
const token = propertyRevision(view, property)
|
|
458
|
+
const entry = running.get(view)?.get(property)
|
|
459
|
+
const initial = currentValue(view, property)
|
|
460
|
+
return () => {
|
|
461
|
+
if (propertyRevision(view, property) !== token) return undefined
|
|
462
|
+
if (entry && running.get(view)?.get(property) === entry)
|
|
463
|
+
return currentValue(view, property)
|
|
464
|
+
return entry?.finished ? entry.track.to : initial
|
|
179
465
|
}
|
|
180
466
|
}
|
|
181
467
|
|
|
182
468
|
/** Stops whatever is animating these properties of the view, or all of them. */
|
|
183
469
|
export function stopView(view: View, properties?: readonly Property[]) {
|
|
470
|
+
const tokens = revisions.get(view)
|
|
471
|
+
for (const property of properties ?? tokens?.keys() ?? [])
|
|
472
|
+
tokens?.set(property, Symbol(property))
|
|
184
473
|
const entries = running.get(view)
|
|
185
474
|
if (!entries) return
|
|
186
475
|
const now = getDriver().now()
|
|
@@ -208,9 +497,14 @@ function release(entry: Entry, now: number, commit: boolean) {
|
|
|
208
497
|
forget(entry)
|
|
209
498
|
if (commit) {
|
|
210
499
|
const value =
|
|
211
|
-
driver.presented(view, property) ?? entry.track.valueAt(now
|
|
500
|
+
driver.presented(view, property) ?? entry.track.valueAt(elapsedOf(entry, now))
|
|
212
501
|
writeValue(view, property, value)
|
|
502
|
+
driver.sync?.(view, property)
|
|
213
503
|
}
|
|
214
504
|
driver.stop(view, entry.handle)
|
|
215
505
|
entry.owner.ended(false)
|
|
216
506
|
}
|
|
507
|
+
|
|
508
|
+
function elapsedOf(entry: Entry, now: number) {
|
|
509
|
+
return entry.owner.elapsed(entry, now)
|
|
510
|
+
}
|