@rafaelaugusto/jolt 0.1.0-beta.0 → 0.1.0-beta.10
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 +441 -25
- package/dist/animate.d.ts +21 -5
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +205 -25
- 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-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-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 +164 -20
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +76 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +303 -23
- package/dist/engine.js.map +1 -1
- 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 +45 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -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 +36 -0
- package/dist/motion-states.d.ts.map +1 -0
- package/dist/motion-states.js +165 -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/presets.d.ts +16 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +24 -6
- package/dist/presets.js.map +1 -1
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +25 -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 +13 -7
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +214 -19
- 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 +17 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +41 -6
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +50 -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 +33 -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/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 +19 -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 +155 -0
- package/src/animate.ts +278 -38
- 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-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-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 +194 -25
- package/src/engine.ts +329 -27
- 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 +61 -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 +215 -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/presets.ts +31 -7
- package/src/press.ts +32 -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 +212 -19
- 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 +63 -6
- package/src/types.ts +51 -1
- package/src/vue/index.ts +231 -0
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
declare namespace org.rafaelaugusto.jolt {
|
|
2
|
+
class PhotoZoom {
|
|
3
|
+
constructor(
|
|
4
|
+
image: android.widget.ImageView,
|
|
5
|
+
viewport: android.view.View,
|
|
6
|
+
bitmapWidth: number,
|
|
7
|
+
bitmapHeight: number,
|
|
8
|
+
minimum: number,
|
|
9
|
+
maximum: number,
|
|
10
|
+
doubleTap: number,
|
|
11
|
+
dismiss: boolean,
|
|
12
|
+
reduced: boolean,
|
|
13
|
+
backdrop: android.view.View | null,
|
|
14
|
+
chrome: android.view.View | null,
|
|
15
|
+
previousTouch: android.view.View.OnTouchListener | null,
|
|
16
|
+
previousImageTouch: android.view.View.OnTouchListener | null,
|
|
17
|
+
listener: PhotoZoom.Listener,
|
|
18
|
+
)
|
|
19
|
+
getX(): number
|
|
20
|
+
getY(): number
|
|
21
|
+
getScale(): number
|
|
22
|
+
getDismissProgress(): number
|
|
23
|
+
isDismissPending(): boolean
|
|
24
|
+
zoom(scale: number, x: number, y: number, animated: boolean): void
|
|
25
|
+
reset(animated: boolean): void
|
|
26
|
+
requestDismiss(): void
|
|
27
|
+
setReduced(value: boolean): void
|
|
28
|
+
setActive(value: boolean): void
|
|
29
|
+
stop(): void
|
|
30
|
+
}
|
|
31
|
+
namespace PhotoZoom {
|
|
32
|
+
class Listener {
|
|
33
|
+
constructor(implementation: {
|
|
34
|
+
onDismiss(
|
|
35
|
+
x: number,
|
|
36
|
+
y: number,
|
|
37
|
+
scale: number,
|
|
38
|
+
velocity: number,
|
|
39
|
+
progress: number,
|
|
40
|
+
): void
|
|
41
|
+
onSettled(x: number, y: number, scale: number): void
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class ImageEffect {
|
|
46
|
+
constructor(
|
|
47
|
+
image: android.widget.ImageView,
|
|
48
|
+
source: android.graphics.Bitmap | null,
|
|
49
|
+
destination: android.graphics.Bitmap | null,
|
|
50
|
+
geometry: androidNative.Array<number>,
|
|
51
|
+
mode: number,
|
|
52
|
+
x: number,
|
|
53
|
+
y: number,
|
|
54
|
+
amplitude: number,
|
|
55
|
+
radius: number,
|
|
56
|
+
duration: number,
|
|
57
|
+
seed: number,
|
|
58
|
+
previousTouch: android.view.View.OnTouchListener | null,
|
|
59
|
+
listener: ImageEffect.Listener,
|
|
60
|
+
)
|
|
61
|
+
isSupported(): boolean
|
|
62
|
+
getFailureReason(): string
|
|
63
|
+
getProgress(): number
|
|
64
|
+
getFrames(): number
|
|
65
|
+
getTextureBytes(): number
|
|
66
|
+
getLongestFrameNanos(): number
|
|
67
|
+
play(endpoint: number): void
|
|
68
|
+
pause(): void
|
|
69
|
+
resume(): void
|
|
70
|
+
seek(progress: number): void
|
|
71
|
+
finish(endpoint: number): void
|
|
72
|
+
stop(): void
|
|
73
|
+
stopPreservingImage(): void
|
|
74
|
+
}
|
|
75
|
+
namespace ImageEffect {
|
|
76
|
+
class Listener {
|
|
77
|
+
constructor(implementation: { onEnd(completed: boolean, progress: number): void })
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
class KeyboardFollower {
|
|
81
|
+
constructor(
|
|
82
|
+
view: android.view.View,
|
|
83
|
+
offset: number,
|
|
84
|
+
listener: KeyboardFollower.Listener,
|
|
85
|
+
)
|
|
86
|
+
attach(): void
|
|
87
|
+
detach(): void
|
|
88
|
+
}
|
|
89
|
+
namespace KeyboardFollower {
|
|
90
|
+
class Listener {
|
|
91
|
+
constructor(implementation: {
|
|
92
|
+
onStart(): void
|
|
93
|
+
onSettled(translation: number): void
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
class KeyboardObserver {
|
|
98
|
+
constructor(root: android.view.View, listener: KeyboardObserver.Listener)
|
|
99
|
+
attach(): void
|
|
100
|
+
detach(): void
|
|
101
|
+
height(): number
|
|
102
|
+
}
|
|
103
|
+
namespace KeyboardObserver {
|
|
104
|
+
class Listener {
|
|
105
|
+
constructor(implementation: {
|
|
106
|
+
onChange(height: number, duration: number, curve: ArrayLike<number> | null): void
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
class TextReveal {
|
|
111
|
+
constructor(
|
|
112
|
+
view: android.widget.TextView,
|
|
113
|
+
byWord: boolean,
|
|
114
|
+
duration: number,
|
|
115
|
+
stagger: number,
|
|
116
|
+
delay: number,
|
|
117
|
+
offset: number,
|
|
118
|
+
callback: TextReveal.Callback,
|
|
119
|
+
)
|
|
120
|
+
isSupported(): boolean
|
|
121
|
+
start(): void
|
|
122
|
+
cancel(): void
|
|
123
|
+
}
|
|
124
|
+
namespace TextReveal {
|
|
125
|
+
class Callback {
|
|
126
|
+
constructor(implementation: { onEnd(finished: boolean): void })
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
class NumberRoll {
|
|
130
|
+
constructor(
|
|
131
|
+
view: android.widget.TextView,
|
|
132
|
+
starts: androidNative.Array<number>,
|
|
133
|
+
ends: androidNative.Array<number>,
|
|
134
|
+
previous: androidNative.Array<string>,
|
|
135
|
+
direction: number,
|
|
136
|
+
times: androidNative.Array<number>,
|
|
137
|
+
values: androidNative.Array<number>,
|
|
138
|
+
duration: number,
|
|
139
|
+
delay: number,
|
|
140
|
+
stagger: number,
|
|
141
|
+
shift: number,
|
|
142
|
+
fade: number,
|
|
143
|
+
tabular: boolean,
|
|
144
|
+
callback: NumberRoll.Callback,
|
|
145
|
+
)
|
|
146
|
+
isSupported(): boolean
|
|
147
|
+
start(): void
|
|
148
|
+
cancel(): void
|
|
149
|
+
}
|
|
150
|
+
namespace NumberRoll {
|
|
151
|
+
class Callback {
|
|
152
|
+
constructor(implementation: { onEnd(finished: boolean): void })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/animate.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isDecorationProperty,
|
|
3
|
+
validateDecorationTarget,
|
|
4
|
+
validateDecorationFrames,
|
|
5
|
+
} from './decoration.js'
|
|
6
|
+
import { playbackGroup } from './playback-group.js'
|
|
7
|
+
import type { PlaybackAnimation } from './playback.js'
|
|
8
|
+
import { preparePath, type PreparedPath } from './path.js'
|
|
1
9
|
import { View } from '@nativescript/core'
|
|
10
|
+
import {
|
|
11
|
+
isColorProperty,
|
|
12
|
+
parseColor,
|
|
13
|
+
validateColorTarget,
|
|
14
|
+
type ColorKeyframes,
|
|
15
|
+
} from './color.js'
|
|
16
|
+
import { DEFAULT_FRICTION, solveDecay } from './decay.js'
|
|
2
17
|
import {
|
|
3
18
|
arrivalOf,
|
|
4
19
|
setArrival,
|
|
@@ -7,8 +22,9 @@ import {
|
|
|
7
22
|
type Animation,
|
|
8
23
|
type Plan,
|
|
9
24
|
} from './engine.js'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
25
|
+
import { getPreset, type PresetName } from './presets.js'
|
|
26
|
+
import { resolveHaptic, type Haptic } from './haptics.js'
|
|
27
|
+
import { isProperty, precision, type Property } from './properties.js'
|
|
12
28
|
import {
|
|
13
29
|
hasCurve,
|
|
14
30
|
mergeTransitions,
|
|
@@ -19,16 +35,22 @@ import {
|
|
|
19
35
|
} from './transition.js'
|
|
20
36
|
import type {
|
|
21
37
|
AnimateOptions,
|
|
38
|
+
DecayOptions,
|
|
39
|
+
DecayVelocity,
|
|
22
40
|
Keyframes,
|
|
23
41
|
KeyframeValue,
|
|
42
|
+
KeyframeProperty,
|
|
24
43
|
Stagger,
|
|
25
44
|
Target,
|
|
45
|
+
Values,
|
|
26
46
|
} from './types.js'
|
|
27
47
|
|
|
28
48
|
interface Options {
|
|
29
49
|
base: Transition
|
|
30
|
-
overrides: Partial<Record<
|
|
50
|
+
overrides: Partial<Record<KeyframeProperty | 'path', Transition>>
|
|
31
51
|
stagger: number | Stagger | undefined
|
|
52
|
+
relative: boolean | undefined
|
|
53
|
+
haptic: Haptic | undefined
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
/**
|
|
@@ -45,28 +67,158 @@ export function animate(
|
|
|
45
67
|
target: Target | readonly Target[],
|
|
46
68
|
animation: Keyframes | PresetName,
|
|
47
69
|
options?: AnimateOptions | SpringName,
|
|
48
|
-
):
|
|
49
|
-
|
|
70
|
+
): PlaybackAnimation {
|
|
71
|
+
return prepareAnimation(target, animation, options)()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Validate and snapshot a complete animation before changing any view ownership. */
|
|
75
|
+
export function prepareAnimation(
|
|
76
|
+
target: Target | readonly Target[],
|
|
77
|
+
animation: Keyframes | PresetName,
|
|
78
|
+
options?: AnimateOptions | SpringName,
|
|
79
|
+
): () => PlaybackAnimation {
|
|
80
|
+
const { keyframes, base, overrides, stagger, relative, haptic, path } = prepare(
|
|
81
|
+
animation,
|
|
82
|
+
options,
|
|
83
|
+
)
|
|
84
|
+
const feedback = resolveHaptic(haptic)
|
|
50
85
|
|
|
51
86
|
if (!isList(target)) {
|
|
52
|
-
|
|
87
|
+
const view = toView(target)
|
|
88
|
+
for (const property of Object.keys(keyframes))
|
|
89
|
+
if (isColorProperty(property)) validateColorTarget(view, property)
|
|
90
|
+
else if (isDecorationProperty(property)) validateDecorationTarget(view, property)
|
|
91
|
+
const plans = plan(keyframes, base, overrides, 0, relative, path)
|
|
92
|
+
return () => new ViewAnimation(view, plans, feedback)
|
|
53
93
|
}
|
|
54
94
|
|
|
55
95
|
const views = target.map(toView)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
(view,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
96
|
+
for (const view of views)
|
|
97
|
+
for (const property of Object.keys(keyframes))
|
|
98
|
+
if (isColorProperty(property)) validateColorTarget(view, property)
|
|
99
|
+
else if (isDecorationProperty(property)) validateDecorationTarget(view, property)
|
|
100
|
+
const plans = views.map((_, index) =>
|
|
101
|
+
plan(
|
|
102
|
+
keyframes,
|
|
103
|
+
base,
|
|
104
|
+
overrides,
|
|
105
|
+
staggerDelay(stagger, index, views.length),
|
|
106
|
+
relative,
|
|
107
|
+
path,
|
|
63
108
|
),
|
|
64
109
|
)
|
|
110
|
+
return () =>
|
|
111
|
+
playbackGroup(
|
|
112
|
+
views.map((view, index) => new ViewAnimation(view, plans[index]!, feedback, true)),
|
|
113
|
+
)
|
|
65
114
|
}
|
|
66
115
|
|
|
67
|
-
/**
|
|
68
|
-
export function
|
|
69
|
-
|
|
116
|
+
/** Starts at the current translation and slows down from a velocity in DIPs per second. */
|
|
117
|
+
export function decay(
|
|
118
|
+
target: Target | readonly Target[],
|
|
119
|
+
velocity: DecayVelocity,
|
|
120
|
+
options: DecayOptions = {},
|
|
121
|
+
): PlaybackAnimation {
|
|
122
|
+
const friction = options.friction ?? DEFAULT_FRICTION
|
|
123
|
+
if (!Number.isFinite(friction) || friction <= 0 || friction >= 1) {
|
|
124
|
+
throw new RangeError('Decay friction must be between 0 and 1 exclusively.')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const stops = (values: readonly number[] | undefined) => {
|
|
128
|
+
if (values === undefined) return undefined
|
|
129
|
+
if (
|
|
130
|
+
!Array.isArray(values) ||
|
|
131
|
+
values.length === 0 ||
|
|
132
|
+
![...values].every(Number.isFinite)
|
|
133
|
+
) {
|
|
134
|
+
throw new RangeError('Decay snap needs a nonempty array of finite positions.')
|
|
135
|
+
}
|
|
136
|
+
return [...values]
|
|
137
|
+
}
|
|
138
|
+
const snap = options.snap
|
|
139
|
+
if (
|
|
140
|
+
snap !== undefined &&
|
|
141
|
+
!Array.isArray(snap) &&
|
|
142
|
+
(snap === null ||
|
|
143
|
+
typeof snap !== 'object' ||
|
|
144
|
+
Object.keys(snap).some((key) => key !== 'x' && key !== 'y'))
|
|
145
|
+
) {
|
|
146
|
+
throw new TypeError('Decay snap must be an array or an object with x and y stops.')
|
|
147
|
+
}
|
|
148
|
+
const shared = Array.isArray(snap)
|
|
149
|
+
const perAxis = snap as { x?: readonly number[]; y?: readonly number[] } | undefined
|
|
150
|
+
const snapped = shared ? stops(snap) : undefined
|
|
151
|
+
const snapX = shared ? snapped : stops(perAxis?.x)
|
|
152
|
+
const snapY = shared ? snapped : stops(perAxis?.y)
|
|
153
|
+
|
|
154
|
+
const plans = Object.entries(velocity).flatMap<Plan>(([property, value]) => {
|
|
155
|
+
if (property !== 'x' && property !== 'y') {
|
|
156
|
+
throw new Error(`Can't decay "${property}". Use x or y.`)
|
|
157
|
+
}
|
|
158
|
+
if (value === undefined) return []
|
|
159
|
+
if (!Number.isFinite(value)) {
|
|
160
|
+
throw new RangeError(`Invalid decay velocity for "${property}".`)
|
|
161
|
+
}
|
|
162
|
+
// Validate every axis before handing any plans to the engine.
|
|
163
|
+
solveDecay(0, value, friction, precision[property])
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
property,
|
|
167
|
+
keyframes: [null, null],
|
|
168
|
+
transition: {
|
|
169
|
+
curve: {
|
|
170
|
+
type: 'decay',
|
|
171
|
+
friction,
|
|
172
|
+
velocity: value,
|
|
173
|
+
snap: property === 'x' ? snapX : snapY,
|
|
174
|
+
},
|
|
175
|
+
delay: 0,
|
|
176
|
+
repeat: 0,
|
|
177
|
+
reverse: false,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
]
|
|
181
|
+
})
|
|
182
|
+
const views = toViews(target)
|
|
183
|
+
if (plans.length === 0) return playbackGroup([])
|
|
184
|
+
return playbackGroup(
|
|
185
|
+
views.map((view) => new ViewAnimation(view, plans, undefined, true)),
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Stops all or selected properties at their current values. `scale` stops both axes. */
|
|
190
|
+
export function stop(
|
|
191
|
+
target: Target | readonly Target[],
|
|
192
|
+
properties?: readonly KeyframeProperty[],
|
|
193
|
+
) {
|
|
194
|
+
const expanded = properties?.flatMap<Property>((property) =>
|
|
195
|
+
property === 'scale' ? ['scaleX', 'scaleY'] : [property],
|
|
196
|
+
)
|
|
197
|
+
for (const view of toViews(target)) stopView(view, expanded)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Applies values at once, stopping whatever animates those properties.
|
|
202
|
+
* `scale` sets both axes. A view that isn't loaded yet gets them right away.
|
|
203
|
+
*
|
|
204
|
+
* ```ts
|
|
205
|
+
* set(logo, { opacity: 1, scale: 1, y: 0 })
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
export function set(target: Target | readonly Target[], values: Values): void {
|
|
209
|
+
if (values === null || typeof values !== 'object' || Array.isArray(values))
|
|
210
|
+
throw new TypeError('set() takes an object of values.')
|
|
211
|
+
const keyframes: Record<string, unknown> = {}
|
|
212
|
+
for (const [key, value] of Object.entries(values)) {
|
|
213
|
+
if (value === undefined) continue
|
|
214
|
+
if (key !== 'scale' && !isProperty(key)) throw new Error(`Can't set "${key}".`)
|
|
215
|
+
if (value === null || Array.isArray(value))
|
|
216
|
+
throw new TypeError(
|
|
217
|
+
`set() takes one value per property, got ${String(value)} for "${key}".`,
|
|
218
|
+
)
|
|
219
|
+
keyframes[key] = [value, value]
|
|
220
|
+
}
|
|
221
|
+
animate(target, keyframes as Keyframes, { duration: 0 })
|
|
70
222
|
}
|
|
71
223
|
|
|
72
224
|
/** Plays several animations as one: finished once they all are, and stopped together. */
|
|
@@ -103,39 +255,68 @@ function isList<T>(target: T | readonly T[]): target is readonly T[] {
|
|
|
103
255
|
function prepare(
|
|
104
256
|
animation: Keyframes | PresetName,
|
|
105
257
|
options?: AnimateOptions | SpringName,
|
|
258
|
+
) {
|
|
259
|
+
const prepared = resolveAnimation(animation, options)
|
|
260
|
+
return {
|
|
261
|
+
...prepared,
|
|
262
|
+
path: preparePath(prepared.keyframes, prepared.base, prepared.overrides),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function resolveAnimation(
|
|
267
|
+
animation: Keyframes | PresetName,
|
|
268
|
+
options?: AnimateOptions | SpringName,
|
|
106
269
|
) {
|
|
107
270
|
const user = normalize(options)
|
|
108
271
|
if (typeof animation !== 'string') return { keyframes: animation, ...user }
|
|
109
272
|
|
|
110
|
-
const preset =
|
|
111
|
-
if (!preset) throw new Error(`Unknown preset "${animation}".`)
|
|
273
|
+
const preset = getPreset(animation)
|
|
112
274
|
const own = normalize(preset.transition)
|
|
113
275
|
return {
|
|
114
|
-
keyframes: preset.keyframes
|
|
276
|
+
keyframes: preset.keyframes,
|
|
115
277
|
base: mergeTransitions(own.base, user.base),
|
|
116
278
|
overrides: hasCurve(user.base)
|
|
117
279
|
? user.overrides
|
|
118
280
|
: { ...own.overrides, ...user.overrides },
|
|
119
|
-
stagger: user.stagger,
|
|
281
|
+
stagger: user.stagger ?? own.stagger,
|
|
282
|
+
relative: user.relative ?? own.relative,
|
|
283
|
+
haptic: user.haptic ?? own.haptic,
|
|
120
284
|
}
|
|
121
285
|
}
|
|
122
286
|
|
|
123
287
|
function normalize(options: AnimateOptions | SpringName | undefined): Options {
|
|
124
|
-
if (options === undefined)
|
|
288
|
+
if (options === undefined)
|
|
289
|
+
return {
|
|
290
|
+
base: {},
|
|
291
|
+
overrides: {},
|
|
292
|
+
stagger: undefined,
|
|
293
|
+
relative: undefined,
|
|
294
|
+
haptic: undefined,
|
|
295
|
+
}
|
|
125
296
|
if (typeof options === 'string')
|
|
126
|
-
return {
|
|
297
|
+
return {
|
|
298
|
+
base: spring(options),
|
|
299
|
+
overrides: {},
|
|
300
|
+
stagger: undefined,
|
|
301
|
+
relative: undefined,
|
|
302
|
+
haptic: undefined,
|
|
303
|
+
}
|
|
127
304
|
|
|
128
305
|
const base: Record<string, unknown> = {}
|
|
129
306
|
const overrides: Options['overrides'] = {}
|
|
130
307
|
let stagger: Options['stagger']
|
|
308
|
+
let relative: Options['relative']
|
|
309
|
+
let haptic: Options['haptic']
|
|
131
310
|
for (const [key, value] of Object.entries(options)) {
|
|
132
311
|
if (value === undefined) continue
|
|
133
312
|
if (key === 'stagger') stagger = value as Options['stagger']
|
|
134
|
-
else if (key === '
|
|
313
|
+
else if (key === 'relative') relative = value as Options['relative']
|
|
314
|
+
else if (key === 'haptic') haptic = value as Options['haptic']
|
|
315
|
+
else if (key === 'path' || key === 'scale' || isProperty(key)) {
|
|
135
316
|
overrides[key] = typeof value === 'string' ? spring(value as SpringName) : value
|
|
136
317
|
} else base[key] = value
|
|
137
318
|
}
|
|
138
|
-
return { base, overrides, stagger }
|
|
319
|
+
return { base, overrides, stagger, relative, haptic }
|
|
139
320
|
}
|
|
140
321
|
|
|
141
322
|
function plan(
|
|
@@ -143,8 +324,26 @@ function plan(
|
|
|
143
324
|
base: Transition,
|
|
144
325
|
overrides: Options['overrides'],
|
|
145
326
|
offset: number,
|
|
327
|
+
relative = false,
|
|
328
|
+
path?: PreparedPath,
|
|
146
329
|
): Plan[] {
|
|
147
|
-
return expand(keyframes).map(([property, frames]) => {
|
|
330
|
+
return expand(keyframes, path).map(([property, frames]) => {
|
|
331
|
+
if (path?.properties.includes(property as 'x' | 'y' | 'rotate')) {
|
|
332
|
+
if (
|
|
333
|
+
!Number.isFinite(path.transition.delay + offset) ||
|
|
334
|
+
path.transition.delay + offset < 0
|
|
335
|
+
)
|
|
336
|
+
throw new RangeError(
|
|
337
|
+
'Path delays, including stagger, must be finite and nonnegative.',
|
|
338
|
+
)
|
|
339
|
+
return {
|
|
340
|
+
property,
|
|
341
|
+
keyframes: frames,
|
|
342
|
+
transition: { ...path.transition, delay: path.transition.delay + offset },
|
|
343
|
+
relative,
|
|
344
|
+
path,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
148
347
|
const override =
|
|
149
348
|
overrides[property] ?? (property.startsWith('scale') ? overrides.scale : undefined)
|
|
150
349
|
const transition = resolveTransition(
|
|
@@ -153,39 +352,79 @@ function plan(
|
|
|
153
352
|
frames.length,
|
|
154
353
|
)
|
|
155
354
|
transition.delay += offset
|
|
156
|
-
return { property, keyframes: frames, transition }
|
|
355
|
+
return { property, keyframes: frames, transition, relative }
|
|
157
356
|
})
|
|
158
357
|
}
|
|
159
358
|
|
|
160
359
|
/** Each property the animation touches, with the value it starts from when it sets one. */
|
|
161
|
-
export function startingValues(
|
|
162
|
-
|
|
163
|
-
|
|
360
|
+
export function startingValues(
|
|
361
|
+
animation: Keyframes | PresetName,
|
|
362
|
+
options?: AnimateOptions | SpringName,
|
|
363
|
+
) {
|
|
364
|
+
const { keyframes, relative, path } = prepare(animation, options)
|
|
164
365
|
if (!keyframes) throw new Error(`Unknown preset "${animation}".`)
|
|
165
366
|
return new Map(
|
|
166
|
-
expand(keyframes
|
|
367
|
+
expand(keyframes, path).map(([property, [first]]) => [
|
|
368
|
+
property,
|
|
369
|
+
relative && !isColorProperty(property) ? null : first!,
|
|
370
|
+
]),
|
|
167
371
|
)
|
|
168
372
|
}
|
|
169
373
|
|
|
170
|
-
|
|
374
|
+
/** Each property the animation touches, with the value it leaves behind when that is known up front. */
|
|
375
|
+
export function endingValues(
|
|
376
|
+
animation: Keyframes | PresetName,
|
|
377
|
+
options?: AnimateOptions | SpringName,
|
|
378
|
+
) {
|
|
379
|
+
const { keyframes, base, overrides, relative, path } = prepare(animation, options)
|
|
380
|
+
return new Map(
|
|
381
|
+
plan(keyframes, base, overrides, 0, relative, path).map(
|
|
382
|
+
({ property, keyframes: frames, transition, path: owned }) => {
|
|
383
|
+
const finite = Number.isFinite(transition.repeat)
|
|
384
|
+
const backwards = transition.reverse && finite && transition.repeat % 2 === 1
|
|
385
|
+
const unknown = owned !== undefined || (relative && !isColorProperty(property))
|
|
386
|
+
const frame = !finite || backwards ? frames[0] : frames.at(-1)
|
|
387
|
+
return [property, unknown ? null : frame!]
|
|
388
|
+
},
|
|
389
|
+
),
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function expand(
|
|
394
|
+
keyframes: Keyframes,
|
|
395
|
+
path?: PreparedPath,
|
|
396
|
+
): [Property, (number | null)[]][] {
|
|
171
397
|
const expanded: [Property, (number | null)[]][] = []
|
|
172
|
-
for (const [key, value] of Object.entries(keyframes)
|
|
173
|
-
|
|
174
|
-
KeyframeValue | undefined,
|
|
175
|
-
][]) {
|
|
176
|
-
if (value === undefined) continue
|
|
398
|
+
for (const [key, value] of Object.entries(keyframes)) {
|
|
399
|
+
if (value === undefined || key === 'path' || key === 'orient') continue
|
|
177
400
|
if (key !== 'scale' && !isProperty(key)) throw new Error(`Can't animate "${key}".`)
|
|
178
401
|
|
|
179
402
|
const properties: Property[] = key === 'scale' ? ['scaleX', 'scaleY'] : [key]
|
|
180
403
|
for (const property of properties) {
|
|
181
404
|
if (key === 'scale' && keyframes[property] !== undefined) continue
|
|
182
|
-
expanded.push([
|
|
405
|
+
expanded.push([
|
|
406
|
+
property,
|
|
407
|
+
toKeyframes(property, value as KeyframeValue | ColorKeyframes),
|
|
408
|
+
])
|
|
183
409
|
}
|
|
184
410
|
}
|
|
411
|
+
for (const [property, value] of path?.starts ?? [])
|
|
412
|
+
expanded.push([property, [value, value]])
|
|
185
413
|
return expanded
|
|
186
414
|
}
|
|
187
415
|
|
|
188
|
-
function toKeyframes(
|
|
416
|
+
function toKeyframes(
|
|
417
|
+
property: Property,
|
|
418
|
+
value: KeyframeValue | ColorKeyframes,
|
|
419
|
+
): (number | null)[] {
|
|
420
|
+
if (isColorProperty(property)) {
|
|
421
|
+
const frames = Array.isArray(value) ? [...value] : [null, value]
|
|
422
|
+
if (!frames.length) throw new TypeError(`Empty color keyframes for "${property}".`)
|
|
423
|
+
if (frames.length === 1) frames.unshift(null)
|
|
424
|
+
return frames.map((frame) => (frame === null ? null : parseColor(frame)))
|
|
425
|
+
}
|
|
426
|
+
if (typeof value !== 'number' && !Array.isArray(value))
|
|
427
|
+
throw new TypeError(`Invalid numeric keyframes for "${property}".`)
|
|
189
428
|
const frames = typeof value === 'number' ? [null, value] : [...value]
|
|
190
429
|
if (frames.length === 1) frames.unshift(null)
|
|
191
430
|
if (
|
|
@@ -194,6 +433,7 @@ function toKeyframes(property: Property, value: KeyframeValue): (number | null)[
|
|
|
194
433
|
) {
|
|
195
434
|
throw new Error(`Invalid keyframes for "${property}": ${JSON.stringify(value)}.`)
|
|
196
435
|
}
|
|
436
|
+
if (isDecorationProperty(property)) validateDecorationFrames(property, frames)
|
|
197
437
|
return frames
|
|
198
438
|
}
|
|
199
439
|
|
package/src/color.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Button, Color, Label, TextBase, isIOS, type View } from '@nativescript/core'
|
|
2
|
+
|
|
3
|
+
export type ColorProperty = 'backgroundColor' | 'borderColor' | 'color'
|
|
4
|
+
export type ColorValue = string | Color
|
|
5
|
+
export type ColorKeyframes = ColorValue | readonly (ColorValue | null)[]
|
|
6
|
+
export const isColorProperty = (property: string): property is ColorProperty =>
|
|
7
|
+
property === 'backgroundColor' || property === 'borderColor' || property === 'color'
|
|
8
|
+
|
|
9
|
+
export function parseColor(value: unknown): number {
|
|
10
|
+
if (value instanceof Color) return value.argb >>> 0
|
|
11
|
+
if (typeof value !== 'string' || !Color.isValid(value))
|
|
12
|
+
throw new TypeError(`Invalid animation color: ${String(value)}.`)
|
|
13
|
+
return new Color(value).argb >>> 0
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readColor(view: View, property: ColorProperty): number {
|
|
17
|
+
const value = property === 'borderColor' ? view.borderTopColor : view[property]
|
|
18
|
+
return value instanceof Color
|
|
19
|
+
? value.argb >>> 0
|
|
20
|
+
: property === 'backgroundColor'
|
|
21
|
+
? 0
|
|
22
|
+
: 0xff000000
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function validateColorTarget(view: View, property: ColorProperty) {
|
|
26
|
+
if (
|
|
27
|
+
property === 'color' &&
|
|
28
|
+
!(isIOS ? view instanceof Label || view instanceof Button : view instanceof TextBase)
|
|
29
|
+
)
|
|
30
|
+
throw new TypeError(
|
|
31
|
+
'Text color animation requires a Label or Button on iOS, or a TextBase on Android.',
|
|
32
|
+
)
|
|
33
|
+
if (property === 'borderColor') {
|
|
34
|
+
const colors = [
|
|
35
|
+
view.borderTopColor,
|
|
36
|
+
view.borderRightColor,
|
|
37
|
+
view.borderBottomColor,
|
|
38
|
+
view.borderLeftColor,
|
|
39
|
+
]
|
|
40
|
+
if (
|
|
41
|
+
colors.some(
|
|
42
|
+
(color) => (color?.argb ?? 0xff000000) !== (colors[0]?.argb ?? 0xff000000),
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
throw new TypeError(
|
|
46
|
+
'borderColor animation requires the same starting color on every edge.',
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const clamp = (value: number) => Math.max(0, Math.min(1, value))
|
|
52
|
+
const linear = (channel: number) =>
|
|
53
|
+
channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
|
|
54
|
+
const encoded = (channel: number) =>
|
|
55
|
+
channel <= 0.0031308 ? 12.92 * channel : 1.055 * channel ** (1 / 2.4) - 0.055
|
|
56
|
+
|
|
57
|
+
function lab(value: number) {
|
|
58
|
+
const r = linear(((value >>> 16) & 255) / 255)
|
|
59
|
+
const g = linear(((value >>> 8) & 255) / 255)
|
|
60
|
+
const b = linear((value & 255) / 255)
|
|
61
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b)
|
|
62
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b)
|
|
63
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b)
|
|
64
|
+
return [
|
|
65
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
66
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
67
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
68
|
+
] as const
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Premultiplied OKLab interpolation, clipped to the sRGB output gamut. */
|
|
72
|
+
export function colorMixer(from: number, to: number): (progress: number) => number {
|
|
73
|
+
const a = lab(from),
|
|
74
|
+
b = lab(to)
|
|
75
|
+
const alphaA = (from >>> 24) / 255,
|
|
76
|
+
alphaB = (to >>> 24) / 255
|
|
77
|
+
return (progress) => {
|
|
78
|
+
const p = clamp(progress)
|
|
79
|
+
if (p === 0) return from
|
|
80
|
+
if (p === 1) return to
|
|
81
|
+
const alpha = alphaA + (alphaB - alphaA) * p
|
|
82
|
+
if (alpha <= 0) return 0
|
|
83
|
+
const channel = (i: number) => (a[i]! * alphaA * (1 - p) + b[i]! * alphaB * p) / alpha
|
|
84
|
+
const L = channel(0),
|
|
85
|
+
A = channel(1),
|
|
86
|
+
B = channel(2)
|
|
87
|
+
const l = (L + 0.3963377774 * A + 0.2158037573 * B) ** 3
|
|
88
|
+
const m = (L - 0.1055613458 * A - 0.0638541728 * B) ** 3
|
|
89
|
+
const s = (L - 0.0894841775 * A - 1.291485548 * B) ** 3
|
|
90
|
+
const r = Math.round(
|
|
91
|
+
clamp(encoded(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s)) * 255,
|
|
92
|
+
)
|
|
93
|
+
const g = Math.round(
|
|
94
|
+
clamp(encoded(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s)) * 255,
|
|
95
|
+
)
|
|
96
|
+
const blue = Math.round(
|
|
97
|
+
clamp(encoded(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)) * 255,
|
|
98
|
+
)
|
|
99
|
+
return ((Math.round(alpha * 255) << 24) | (r << 16) | (g << 8) | blue) >>> 0
|
|
100
|
+
}
|
|
101
|
+
}
|