@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.
Files changed (307) hide show
  1. package/README.md +441 -25
  2. package/dist/animate.d.ts +21 -5
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +205 -25
  5. package/dist/animate.js.map +1 -1
  6. package/dist/color.d.ts +11 -0
  7. package/dist/color.d.ts.map +1 -0
  8. package/dist/color.js +73 -0
  9. package/dist/color.js.map +1 -0
  10. package/dist/config.d.ts +31 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +73 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/decay.d.ts +11 -0
  15. package/dist/decay.d.ts.map +1 -0
  16. package/dist/decay.js +22 -0
  17. package/dist/decay.js.map +1 -0
  18. package/dist/decoration.d.ts +18 -0
  19. package/dist/decoration.d.ts.map +1 -0
  20. package/dist/decoration.js +155 -0
  21. package/dist/decoration.js.map +1 -0
  22. package/dist/drag-input.d.ts +15 -0
  23. package/dist/drag-input.d.ts.map +1 -0
  24. package/dist/drag-input.js +66 -0
  25. package/dist/drag-input.js.map +1 -0
  26. package/dist/drag-physics.d.ts +20 -0
  27. package/dist/drag-physics.d.ts.map +1 -0
  28. package/dist/drag-physics.js +75 -0
  29. package/dist/drag-physics.js.map +1 -0
  30. package/dist/drag.d.ts +58 -0
  31. package/dist/drag.d.ts.map +1 -0
  32. package/dist/drag.js +324 -0
  33. package/dist/drag.js.map +1 -0
  34. package/dist/driver/android-color.d.ts +20 -0
  35. package/dist/driver/android-color.d.ts.map +1 -0
  36. package/dist/driver/android-color.js +136 -0
  37. package/dist/driver/android-color.js.map +1 -0
  38. package/dist/driver/android-decoration.d.ts +22 -0
  39. package/dist/driver/android-decoration.d.ts.map +1 -0
  40. package/dist/driver/android-decoration.js +139 -0
  41. package/dist/driver/android-decoration.js.map +1 -0
  42. package/dist/driver/android-playback.d.ts +13 -0
  43. package/dist/driver/android-playback.d.ts.map +1 -0
  44. package/dist/driver/android-playback.js +141 -0
  45. package/dist/driver/android-playback.js.map +1 -0
  46. package/dist/driver/android.d.ts +1 -1
  47. package/dist/driver/android.d.ts.map +1 -1
  48. package/dist/driver/android.js +165 -13
  49. package/dist/driver/android.js.map +1 -1
  50. package/dist/driver/index.d.ts +25 -2
  51. package/dist/driver/index.d.ts.map +1 -1
  52. package/dist/driver/index.js.map +1 -1
  53. package/dist/driver/ios-color.d.ts +16 -0
  54. package/dist/driver/ios-color.d.ts.map +1 -0
  55. package/dist/driver/ios-color.js +392 -0
  56. package/dist/driver/ios-color.js.map +1 -0
  57. package/dist/driver/ios-decoration.d.ts +17 -0
  58. package/dist/driver/ios-decoration.d.ts.map +1 -0
  59. package/dist/driver/ios-decoration.js +271 -0
  60. package/dist/driver/ios-decoration.js.map +1 -0
  61. package/dist/driver/ios-layout.d.ts +9 -0
  62. package/dist/driver/ios-layout.d.ts.map +1 -0
  63. package/dist/driver/ios-layout.js +52 -0
  64. package/dist/driver/ios-layout.js.map +1 -0
  65. package/dist/driver/ios-playback.d.ts +10 -0
  66. package/dist/driver/ios-playback.d.ts.map +1 -0
  67. package/dist/driver/ios-playback.js +47 -0
  68. package/dist/driver/ios-playback.js.map +1 -0
  69. package/dist/driver/ios-shadow.d.ts +6 -0
  70. package/dist/driver/ios-shadow.d.ts.map +1 -0
  71. package/dist/driver/ios-shadow.js +52 -0
  72. package/dist/driver/ios-shadow.js.map +1 -0
  73. package/dist/driver/ios.d.ts.map +1 -1
  74. package/dist/driver/ios.js +164 -20
  75. package/dist/driver/ios.js.map +1 -1
  76. package/dist/engine.d.ts +76 -5
  77. package/dist/engine.d.ts.map +1 -1
  78. package/dist/engine.js +303 -23
  79. package/dist/engine.js.map +1 -1
  80. package/dist/haptics.d.ts +21 -0
  81. package/dist/haptics.d.ts.map +1 -0
  82. package/dist/haptics.js +120 -0
  83. package/dist/haptics.js.map +1 -0
  84. package/dist/image-effect-native.d.ts +26 -0
  85. package/dist/image-effect-native.d.ts.map +1 -0
  86. package/dist/image-effect-native.js +73 -0
  87. package/dist/image-effect-native.js.map +1 -0
  88. package/dist/image-effect.d.ts +50 -0
  89. package/dist/image-effect.d.ts.map +1 -0
  90. package/dist/image-effect.js +290 -0
  91. package/dist/image-effect.js.map +1 -0
  92. package/dist/image-owner.d.ts +3 -0
  93. package/dist/image-owner.d.ts.map +1 -0
  94. package/dist/image-owner.js +11 -0
  95. package/dist/image-owner.js.map +1 -0
  96. package/dist/index.d.ts +45 -4
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +21 -2
  99. package/dist/index.js.map +1 -1
  100. package/dist/interaction-surface.d.ts +12 -0
  101. package/dist/interaction-surface.d.ts.map +1 -0
  102. package/dist/interaction-surface.js +36 -0
  103. package/dist/interaction-surface.js.map +1 -0
  104. package/dist/items.d.ts +20 -0
  105. package/dist/items.d.ts.map +1 -0
  106. package/dist/items.js +109 -0
  107. package/dist/items.js.map +1 -0
  108. package/dist/keyboard.d.ts +41 -0
  109. package/dist/keyboard.d.ts.map +1 -0
  110. package/dist/keyboard.js +256 -0
  111. package/dist/keyboard.js.map +1 -0
  112. package/dist/label.d.ts +7 -0
  113. package/dist/label.d.ts.map +1 -0
  114. package/dist/label.js +60 -0
  115. package/dist/label.js.map +1 -0
  116. package/dist/layout.d.ts +13 -0
  117. package/dist/layout.d.ts.map +1 -0
  118. package/dist/layout.js +348 -0
  119. package/dist/layout.js.map +1 -0
  120. package/dist/liquid-grid.d.ts +3 -0
  121. package/dist/liquid-grid.d.ts.map +1 -0
  122. package/dist/liquid-grid.js +24 -0
  123. package/dist/liquid-grid.js.map +1 -0
  124. package/dist/liquid.d.ts +18 -0
  125. package/dist/liquid.d.ts.map +1 -0
  126. package/dist/liquid.js +234 -0
  127. package/dist/liquid.js.map +1 -0
  128. package/dist/loader.d.ts +19 -0
  129. package/dist/loader.d.ts.map +1 -0
  130. package/dist/loader.js +173 -0
  131. package/dist/loader.js.map +1 -0
  132. package/dist/motion-scope.d.ts +39 -0
  133. package/dist/motion-scope.d.ts.map +1 -0
  134. package/dist/motion-scope.js +238 -0
  135. package/dist/motion-scope.js.map +1 -0
  136. package/dist/motion-states.d.ts +36 -0
  137. package/dist/motion-states.d.ts.map +1 -0
  138. package/dist/motion-states.js +165 -0
  139. package/dist/motion-states.js.map +1 -0
  140. package/dist/particles.d.ts +39 -0
  141. package/dist/particles.d.ts.map +1 -0
  142. package/dist/particles.js +265 -0
  143. package/dist/particles.js.map +1 -0
  144. package/dist/path.d.ts +12 -0
  145. package/dist/path.d.ts.map +1 -0
  146. package/dist/path.js +90 -0
  147. package/dist/path.js.map +1 -0
  148. package/dist/playback-group.d.ts +6 -0
  149. package/dist/playback-group.d.ts.map +1 -0
  150. package/dist/playback-group.js +141 -0
  151. package/dist/playback-group.js.map +1 -0
  152. package/dist/playback.d.ts +25 -0
  153. package/dist/playback.d.ts.map +1 -0
  154. package/dist/playback.js +35 -0
  155. package/dist/playback.js.map +1 -0
  156. package/dist/presets.d.ts +16 -1
  157. package/dist/presets.d.ts.map +1 -1
  158. package/dist/presets.js +24 -6
  159. package/dist/presets.js.map +1 -1
  160. package/dist/press.d.ts.map +1 -1
  161. package/dist/press.js +25 -7
  162. package/dist/press.js.map +1 -1
  163. package/dist/properties.d.ts +6 -3
  164. package/dist/properties.d.ts.map +1 -1
  165. package/dist/properties.js +26 -2
  166. package/dist/properties.js.map +1 -1
  167. package/dist/reveal.d.ts +25 -0
  168. package/dist/reveal.d.ts.map +1 -0
  169. package/dist/reveal.js +294 -0
  170. package/dist/reveal.js.map +1 -0
  171. package/dist/roll-diff.d.ts +22 -0
  172. package/dist/roll-diff.d.ts.map +1 -0
  173. package/dist/roll-diff.js +36 -0
  174. package/dist/roll-diff.js.map +1 -0
  175. package/dist/roll.d.ts +29 -0
  176. package/dist/roll.d.ts.map +1 -0
  177. package/dist/roll.js +442 -0
  178. package/dist/roll.js.map +1 -0
  179. package/dist/scroll-linked.d.ts +10 -0
  180. package/dist/scroll-linked.d.ts.map +1 -0
  181. package/dist/scroll-linked.js +58 -0
  182. package/dist/scroll-linked.js.map +1 -0
  183. package/dist/sequence.d.ts +13 -7
  184. package/dist/sequence.d.ts.map +1 -1
  185. package/dist/sequence.js +214 -19
  186. package/dist/sequence.js.map +1 -1
  187. package/dist/shimmer.d.ts +12 -0
  188. package/dist/shimmer.d.ts.map +1 -0
  189. package/dist/shimmer.js +161 -0
  190. package/dist/shimmer.js.map +1 -0
  191. package/dist/svg-path.d.ts +14 -0
  192. package/dist/svg-path.d.ts.map +1 -0
  193. package/dist/svg-path.js +281 -0
  194. package/dist/svg-path.js.map +1 -0
  195. package/dist/symbol-effect.d.ts +23 -0
  196. package/dist/symbol-effect.d.ts.map +1 -0
  197. package/dist/symbol-effect.js +187 -0
  198. package/dist/symbol-effect.js.map +1 -0
  199. package/dist/text-layout.d.ts +30 -0
  200. package/dist/text-layout.d.ts.map +1 -0
  201. package/dist/text-layout.js +185 -0
  202. package/dist/text-layout.js.map +1 -0
  203. package/dist/text.d.ts +17 -0
  204. package/dist/text.d.ts.map +1 -0
  205. package/dist/text.js +263 -0
  206. package/dist/text.js.map +1 -0
  207. package/dist/track.d.ts +3 -1
  208. package/dist/track.d.ts.map +1 -1
  209. package/dist/track.js +266 -13
  210. package/dist/track.js.map +1 -1
  211. package/dist/transition.d.ts +17 -1
  212. package/dist/transition.d.ts.map +1 -1
  213. package/dist/transition.js +41 -6
  214. package/dist/transition.js.map +1 -1
  215. package/dist/types.d.ts +50 -1
  216. package/dist/types.d.ts.map +1 -1
  217. package/dist/vue/index.d.ts +95 -0
  218. package/dist/vue/index.d.ts.map +1 -0
  219. package/dist/vue/index.js +163 -0
  220. package/dist/vue/index.js.map +1 -0
  221. package/dist/zoom-native.d.ts +30 -0
  222. package/dist/zoom-native.d.ts.map +1 -0
  223. package/dist/zoom-native.js +56 -0
  224. package/dist/zoom-native.js.map +1 -0
  225. package/dist/zoomable.d.ts +48 -0
  226. package/dist/zoomable.d.ts.map +1 -0
  227. package/dist/zoomable.js +249 -0
  228. package/dist/zoomable.js.map +1 -0
  229. package/package.json +33 -5
  230. package/platforms/android/include.gradle +7 -0
  231. package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
  232. package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
  233. package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
  234. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
  235. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardObserver.java +97 -0
  236. package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
  237. package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
  238. package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
  239. package/platforms/android/proguard-rules.pro +19 -0
  240. package/platforms/ios/build.xcconfig +1 -0
  241. package/platforms/ios/src/JoltImageEffect.h +25 -0
  242. package/platforms/ios/src/JoltImageEffect.mm +242 -0
  243. package/platforms/ios/src/JoltInteractiveAnimation.h +18 -0
  244. package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
  245. package/platforms/ios/src/JoltPhotoZoom.h +23 -0
  246. package/platforms/ios/src/JoltPhotoZoom.m +229 -0
  247. package/platforms/ios/src/module.modulemap +6 -0
  248. package/src/android-plugin.d.ts +155 -0
  249. package/src/animate.ts +278 -38
  250. package/src/color.ts +101 -0
  251. package/src/config.ts +84 -0
  252. package/src/decay.ts +45 -0
  253. package/src/decoration.ts +180 -0
  254. package/src/drag-input.ts +74 -0
  255. package/src/drag-physics.ts +110 -0
  256. package/src/drag.ts +395 -0
  257. package/src/driver/android-color.ts +170 -0
  258. package/src/driver/android-decoration.ts +182 -0
  259. package/src/driver/android-playback.ts +157 -0
  260. package/src/driver/android.ts +208 -20
  261. package/src/driver/index.ts +37 -2
  262. package/src/driver/ios-color.ts +441 -0
  263. package/src/driver/ios-decoration.ts +332 -0
  264. package/src/driver/ios-layout.ts +63 -0
  265. package/src/driver/ios-playback.ts +51 -0
  266. package/src/driver/ios-shadow.ts +50 -0
  267. package/src/driver/ios.ts +194 -25
  268. package/src/engine.ts +329 -27
  269. package/src/haptics.ts +148 -0
  270. package/src/image-effect-native.ts +129 -0
  271. package/src/image-effect.ts +336 -0
  272. package/src/image-owner.ts +12 -0
  273. package/src/index.ts +61 -4
  274. package/src/interaction-surface.ts +41 -0
  275. package/src/ios-plugin.d.ts +81 -0
  276. package/src/items.ts +140 -0
  277. package/src/keyboard.ts +291 -0
  278. package/src/label.ts +76 -0
  279. package/src/layout.ts +408 -0
  280. package/src/liquid-grid.ts +28 -0
  281. package/src/liquid.ts +287 -0
  282. package/src/loader.ts +202 -0
  283. package/src/motion-scope.ts +279 -0
  284. package/src/motion-states.ts +215 -0
  285. package/src/particles.ts +323 -0
  286. package/src/path.ts +124 -0
  287. package/src/playback-group.ts +143 -0
  288. package/src/playback.ts +62 -0
  289. package/src/presets.ts +31 -7
  290. package/src/press.ts +32 -10
  291. package/src/properties.ts +32 -6
  292. package/src/reveal.ts +374 -0
  293. package/src/roll-diff.ts +57 -0
  294. package/src/roll.ts +515 -0
  295. package/src/scroll-linked.ts +73 -0
  296. package/src/sequence.ts +212 -19
  297. package/src/shimmer.ts +180 -0
  298. package/src/svg-path.ts +355 -0
  299. package/src/symbol-effect.ts +226 -0
  300. package/src/text-layout.ts +228 -0
  301. package/src/text.ts +279 -0
  302. package/src/track.ts +347 -12
  303. package/src/transition.ts +63 -6
  304. package/src/types.ts +51 -1
  305. package/src/vue/index.ts +231 -0
  306. package/src/zoom-native.ts +124 -0
  307. 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 { presets, type PresetName } from './presets.js'
11
- import { isProperty, type Property } from './properties.js'
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<keyof Keyframes, Transition>>
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
- ): Animation {
49
- const { keyframes, base, overrides, stagger } = prepare(animation, options)
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
- return new ViewAnimation(toView(target), plan(keyframes, base, overrides, 0))
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
- return group(
57
- views.map(
58
- (view, index) =>
59
- new ViewAnimation(
60
- view,
61
- plan(keyframes, base, overrides, staggerDelay(stagger, index, views.length)),
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
- /** Stops every animation running on the view, leaving it where it is. */
68
- export function stop(target: Target | readonly Target[]) {
69
- for (const view of toViews(target)) stopView(view)
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 = presets[animation]
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 as 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) return { base: {}, overrides: {}, stagger: 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 { base: spring(options), overrides: {}, stagger: undefined }
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 === 'scale' || isProperty(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(animation: Keyframes | PresetName) {
162
- const keyframes =
163
- typeof animation === 'string' ? presets[animation]?.keyframes : animation
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 as Keyframes).map(([property, [first]]) => [property, first!]),
367
+ expand(keyframes, path).map(([property, [first]]) => [
368
+ property,
369
+ relative && !isColorProperty(property) ? null : first!,
370
+ ]),
167
371
  )
168
372
  }
169
373
 
170
- function expand(keyframes: Keyframes): [Property, (number | null)[]][] {
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) as [
173
- keyof Keyframes,
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([property, toKeyframes(property, value)])
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(property: Property, value: KeyframeValue): (number | null)[] {
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
+ }