@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.
Files changed (318) hide show
  1. package/README.md +385 -18
  2. package/dist/animate.d.ts +19 -3
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +186 -14
  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-gradient.d.ts +5 -0
  43. package/dist/driver/android-gradient.d.ts.map +1 -0
  44. package/dist/driver/android-gradient.js +89 -0
  45. package/dist/driver/android-gradient.js.map +1 -0
  46. package/dist/driver/android-playback.d.ts +13 -0
  47. package/dist/driver/android-playback.d.ts.map +1 -0
  48. package/dist/driver/android-playback.js +141 -0
  49. package/dist/driver/android-playback.js.map +1 -0
  50. package/dist/driver/android.d.ts +1 -1
  51. package/dist/driver/android.d.ts.map +1 -1
  52. package/dist/driver/android.js +165 -13
  53. package/dist/driver/android.js.map +1 -1
  54. package/dist/driver/index.d.ts +25 -2
  55. package/dist/driver/index.d.ts.map +1 -1
  56. package/dist/driver/index.js.map +1 -1
  57. package/dist/driver/ios-color.d.ts +16 -0
  58. package/dist/driver/ios-color.d.ts.map +1 -0
  59. package/dist/driver/ios-color.js +392 -0
  60. package/dist/driver/ios-color.js.map +1 -0
  61. package/dist/driver/ios-decoration.d.ts +17 -0
  62. package/dist/driver/ios-decoration.d.ts.map +1 -0
  63. package/dist/driver/ios-decoration.js +271 -0
  64. package/dist/driver/ios-decoration.js.map +1 -0
  65. package/dist/driver/ios-gradient.d.ts +5 -0
  66. package/dist/driver/ios-gradient.d.ts.map +1 -0
  67. package/dist/driver/ios-gradient.js +109 -0
  68. package/dist/driver/ios-gradient.js.map +1 -0
  69. package/dist/driver/ios-layout.d.ts +9 -0
  70. package/dist/driver/ios-layout.d.ts.map +1 -0
  71. package/dist/driver/ios-layout.js +52 -0
  72. package/dist/driver/ios-layout.js.map +1 -0
  73. package/dist/driver/ios-playback.d.ts +10 -0
  74. package/dist/driver/ios-playback.d.ts.map +1 -0
  75. package/dist/driver/ios-playback.js +47 -0
  76. package/dist/driver/ios-playback.js.map +1 -0
  77. package/dist/driver/ios-shadow.d.ts +6 -0
  78. package/dist/driver/ios-shadow.d.ts.map +1 -0
  79. package/dist/driver/ios-shadow.js +52 -0
  80. package/dist/driver/ios-shadow.js.map +1 -0
  81. package/dist/driver/ios.d.ts.map +1 -1
  82. package/dist/driver/ios.js +163 -28
  83. package/dist/driver/ios.js.map +1 -1
  84. package/dist/engine.d.ts +75 -5
  85. package/dist/engine.d.ts.map +1 -1
  86. package/dist/engine.js +293 -23
  87. package/dist/engine.js.map +1 -1
  88. package/dist/gradient.d.ts +90 -0
  89. package/dist/gradient.d.ts.map +1 -0
  90. package/dist/gradient.js +247 -0
  91. package/dist/gradient.js.map +1 -0
  92. package/dist/haptics.d.ts +21 -0
  93. package/dist/haptics.d.ts.map +1 -0
  94. package/dist/haptics.js +120 -0
  95. package/dist/haptics.js.map +1 -0
  96. package/dist/image-effect-native.d.ts +26 -0
  97. package/dist/image-effect-native.d.ts.map +1 -0
  98. package/dist/image-effect-native.js +73 -0
  99. package/dist/image-effect-native.js.map +1 -0
  100. package/dist/image-effect.d.ts +50 -0
  101. package/dist/image-effect.d.ts.map +1 -0
  102. package/dist/image-effect.js +290 -0
  103. package/dist/image-effect.js.map +1 -0
  104. package/dist/image-owner.d.ts +3 -0
  105. package/dist/image-owner.d.ts.map +1 -0
  106. package/dist/image-owner.js +11 -0
  107. package/dist/image-owner.js.map +1 -0
  108. package/dist/index.d.ts +47 -4
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +22 -2
  111. package/dist/index.js.map +1 -1
  112. package/dist/interaction-surface.d.ts +12 -0
  113. package/dist/interaction-surface.d.ts.map +1 -0
  114. package/dist/interaction-surface.js +36 -0
  115. package/dist/interaction-surface.js.map +1 -0
  116. package/dist/items.d.ts +20 -0
  117. package/dist/items.d.ts.map +1 -0
  118. package/dist/items.js +109 -0
  119. package/dist/items.js.map +1 -0
  120. package/dist/keyboard.d.ts +41 -0
  121. package/dist/keyboard.d.ts.map +1 -0
  122. package/dist/keyboard.js +256 -0
  123. package/dist/keyboard.js.map +1 -0
  124. package/dist/label.d.ts +7 -0
  125. package/dist/label.d.ts.map +1 -0
  126. package/dist/label.js +60 -0
  127. package/dist/label.js.map +1 -0
  128. package/dist/layout.d.ts +13 -0
  129. package/dist/layout.d.ts.map +1 -0
  130. package/dist/layout.js +348 -0
  131. package/dist/layout.js.map +1 -0
  132. package/dist/liquid-grid.d.ts +3 -0
  133. package/dist/liquid-grid.d.ts.map +1 -0
  134. package/dist/liquid-grid.js +24 -0
  135. package/dist/liquid-grid.js.map +1 -0
  136. package/dist/liquid.d.ts +18 -0
  137. package/dist/liquid.d.ts.map +1 -0
  138. package/dist/liquid.js +234 -0
  139. package/dist/liquid.js.map +1 -0
  140. package/dist/loader.d.ts +19 -0
  141. package/dist/loader.d.ts.map +1 -0
  142. package/dist/loader.js +173 -0
  143. package/dist/loader.js.map +1 -0
  144. package/dist/motion-scope.d.ts +39 -0
  145. package/dist/motion-scope.d.ts.map +1 -0
  146. package/dist/motion-scope.js +238 -0
  147. package/dist/motion-scope.js.map +1 -0
  148. package/dist/motion-states.d.ts +43 -0
  149. package/dist/motion-states.d.ts.map +1 -0
  150. package/dist/motion-states.js +220 -0
  151. package/dist/motion-states.js.map +1 -0
  152. package/dist/particles.d.ts +39 -0
  153. package/dist/particles.d.ts.map +1 -0
  154. package/dist/particles.js +265 -0
  155. package/dist/particles.js.map +1 -0
  156. package/dist/path.d.ts +12 -0
  157. package/dist/path.d.ts.map +1 -0
  158. package/dist/path.js +90 -0
  159. package/dist/path.js.map +1 -0
  160. package/dist/playback-group.d.ts +6 -0
  161. package/dist/playback-group.d.ts.map +1 -0
  162. package/dist/playback-group.js +141 -0
  163. package/dist/playback-group.js.map +1 -0
  164. package/dist/playback.d.ts +25 -0
  165. package/dist/playback.d.ts.map +1 -0
  166. package/dist/playback.js +35 -0
  167. package/dist/playback.js.map +1 -0
  168. package/dist/press.d.ts.map +1 -1
  169. package/dist/press.js +23 -7
  170. package/dist/press.js.map +1 -1
  171. package/dist/properties.d.ts +6 -3
  172. package/dist/properties.d.ts.map +1 -1
  173. package/dist/properties.js +26 -2
  174. package/dist/properties.js.map +1 -1
  175. package/dist/reveal.d.ts +25 -0
  176. package/dist/reveal.d.ts.map +1 -0
  177. package/dist/reveal.js +294 -0
  178. package/dist/reveal.js.map +1 -0
  179. package/dist/roll-diff.d.ts +22 -0
  180. package/dist/roll-diff.d.ts.map +1 -0
  181. package/dist/roll-diff.js +36 -0
  182. package/dist/roll-diff.js.map +1 -0
  183. package/dist/roll.d.ts +29 -0
  184. package/dist/roll.d.ts.map +1 -0
  185. package/dist/roll.js +442 -0
  186. package/dist/roll.js.map +1 -0
  187. package/dist/scroll-linked.d.ts +10 -0
  188. package/dist/scroll-linked.d.ts.map +1 -0
  189. package/dist/scroll-linked.js +58 -0
  190. package/dist/scroll-linked.js.map +1 -0
  191. package/dist/sequence.d.ts +10 -5
  192. package/dist/sequence.d.ts.map +1 -1
  193. package/dist/sequence.js +208 -14
  194. package/dist/sequence.js.map +1 -1
  195. package/dist/shimmer.d.ts +12 -0
  196. package/dist/shimmer.d.ts.map +1 -0
  197. package/dist/shimmer.js +161 -0
  198. package/dist/shimmer.js.map +1 -0
  199. package/dist/svg-path.d.ts +14 -0
  200. package/dist/svg-path.d.ts.map +1 -0
  201. package/dist/svg-path.js +281 -0
  202. package/dist/svg-path.js.map +1 -0
  203. package/dist/symbol-effect.d.ts +23 -0
  204. package/dist/symbol-effect.d.ts.map +1 -0
  205. package/dist/symbol-effect.js +187 -0
  206. package/dist/symbol-effect.js.map +1 -0
  207. package/dist/text-layout.d.ts +30 -0
  208. package/dist/text-layout.d.ts.map +1 -0
  209. package/dist/text-layout.js +185 -0
  210. package/dist/text-layout.js.map +1 -0
  211. package/dist/text.d.ts +17 -0
  212. package/dist/text.d.ts.map +1 -0
  213. package/dist/text.js +263 -0
  214. package/dist/text.js.map +1 -0
  215. package/dist/track.d.ts +3 -1
  216. package/dist/track.d.ts.map +1 -1
  217. package/dist/track.js +266 -13
  218. package/dist/track.js.map +1 -1
  219. package/dist/transition.d.ts +32 -1
  220. package/dist/transition.d.ts.map +1 -1
  221. package/dist/transition.js +58 -6
  222. package/dist/transition.js.map +1 -1
  223. package/dist/types.d.ts +48 -1
  224. package/dist/types.d.ts.map +1 -1
  225. package/dist/vue/index.d.ts +95 -0
  226. package/dist/vue/index.d.ts.map +1 -0
  227. package/dist/vue/index.js +163 -0
  228. package/dist/vue/index.js.map +1 -0
  229. package/dist/zoom-native.d.ts +30 -0
  230. package/dist/zoom-native.d.ts.map +1 -0
  231. package/dist/zoom-native.js +56 -0
  232. package/dist/zoom-native.js.map +1 -0
  233. package/dist/zoomable.d.ts +48 -0
  234. package/dist/zoomable.d.ts.map +1 -0
  235. package/dist/zoomable.js +249 -0
  236. package/dist/zoomable.js.map +1 -0
  237. package/package.json +29 -5
  238. package/platforms/android/include.gradle +7 -0
  239. package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
  240. package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
  241. package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
  242. package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
  243. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
  244. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardObserver.java +97 -0
  245. package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
  246. package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
  247. package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
  248. package/platforms/android/proguard-rules.pro +21 -0
  249. package/platforms/ios/build.xcconfig +1 -0
  250. package/platforms/ios/src/JoltImageEffect.h +25 -0
  251. package/platforms/ios/src/JoltImageEffect.mm +242 -0
  252. package/platforms/ios/src/JoltInteractiveAnimation.h +18 -0
  253. package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
  254. package/platforms/ios/src/JoltPhotoZoom.h +23 -0
  255. package/platforms/ios/src/JoltPhotoZoom.m +229 -0
  256. package/platforms/ios/src/module.modulemap +6 -0
  257. package/src/android-plugin.d.ts +184 -0
  258. package/src/animate.ts +249 -36
  259. package/src/color.ts +101 -0
  260. package/src/config.ts +84 -0
  261. package/src/decay.ts +45 -0
  262. package/src/decoration.ts +180 -0
  263. package/src/drag-input.ts +74 -0
  264. package/src/drag-physics.ts +110 -0
  265. package/src/drag.ts +395 -0
  266. package/src/driver/android-color.ts +170 -0
  267. package/src/driver/android-decoration.ts +182 -0
  268. package/src/driver/android-gradient.ts +109 -0
  269. package/src/driver/android-playback.ts +157 -0
  270. package/src/driver/android.ts +208 -20
  271. package/src/driver/index.ts +37 -2
  272. package/src/driver/ios-color.ts +441 -0
  273. package/src/driver/ios-decoration.ts +332 -0
  274. package/src/driver/ios-gradient.ts +114 -0
  275. package/src/driver/ios-layout.ts +63 -0
  276. package/src/driver/ios-playback.ts +51 -0
  277. package/src/driver/ios-shadow.ts +50 -0
  278. package/src/driver/ios.ts +193 -33
  279. package/src/engine.ts +321 -27
  280. package/src/gradient.ts +372 -0
  281. package/src/haptics.ts +148 -0
  282. package/src/image-effect-native.ts +129 -0
  283. package/src/image-effect.ts +336 -0
  284. package/src/image-owner.ts +12 -0
  285. package/src/index.ts +75 -4
  286. package/src/interaction-surface.ts +41 -0
  287. package/src/ios-plugin.d.ts +81 -0
  288. package/src/items.ts +140 -0
  289. package/src/keyboard.ts +291 -0
  290. package/src/label.ts +76 -0
  291. package/src/layout.ts +408 -0
  292. package/src/liquid-grid.ts +28 -0
  293. package/src/liquid.ts +287 -0
  294. package/src/loader.ts +202 -0
  295. package/src/motion-scope.ts +279 -0
  296. package/src/motion-states.ts +269 -0
  297. package/src/particles.ts +323 -0
  298. package/src/path.ts +124 -0
  299. package/src/playback-group.ts +143 -0
  300. package/src/playback.ts +62 -0
  301. package/src/press.ts +26 -10
  302. package/src/properties.ts +32 -6
  303. package/src/reveal.ts +374 -0
  304. package/src/roll-diff.ts +57 -0
  305. package/src/roll.ts +515 -0
  306. package/src/scroll-linked.ts +73 -0
  307. package/src/sequence.ts +204 -14
  308. package/src/shimmer.ts +180 -0
  309. package/src/svg-path.ts +355 -0
  310. package/src/symbol-effect.ts +226 -0
  311. package/src/text-layout.ts +228 -0
  312. package/src/text.ts +279 -0
  313. package/src/track.ts +347 -12
  314. package/src/transition.ts +93 -6
  315. package/src/types.ts +49 -1
  316. package/src/vue/index.ts +231 -0
  317. package/src/zoom-native.ts +124 -0
  318. package/src/zoomable.ts +330 -0
@@ -0,0 +1,279 @@
1
+ import { animate, decay } from './animate.js'
2
+ import { press } from './press.js'
3
+ import { drag } from './drag.js'
4
+ import { animateLayout, type LayoutOptions } from './layout.js'
5
+ import type { SpringName } from './transition.js'
6
+ import { sequence } from './sequence.js'
7
+ import {
8
+ motionStates,
9
+ type MotionState,
10
+ type MotionDefinitions,
11
+ type MotionStates,
12
+ type MotionStatesOptions,
13
+ } from './motion-states.js'
14
+ import type { Target } from './types.js'
15
+
16
+ /** A cleanup function or a handle owned exclusively by one scope. */
17
+ export type MotionResource = (() => void) | { stop(): void } | { dispose(): void }
18
+ type Controlled = {
19
+ pause?: () => void
20
+ play?: () => void
21
+ readonly paused?: boolean
22
+ readonly finished?: Promise<unknown>
23
+ }
24
+ interface Entry {
25
+ resource?: MotionResource
26
+ setup?: () => MotionResource
27
+ resume: boolean
28
+ }
29
+ const owners = new WeakMap<object, object>()
30
+
31
+ export interface MotionScope {
32
+ readonly disposed: boolean
33
+ readonly suspended: boolean
34
+ /** Owned handles/subscriptions, including suspended subscription factories. */
35
+ readonly size: number
36
+ animateLayout: typeof animateLayout
37
+ animate: typeof animate
38
+ decay: typeof decay
39
+ sequence: typeof sequence
40
+ press: typeof press
41
+ drag: typeof drag
42
+ states<
43
+ T extends Record<string, Target | null>,
44
+ const S extends Record<string, MotionState<keyof T>>,
45
+ >(
46
+ targets: T,
47
+ definitions: MotionDefinitions<T, S>,
48
+ options?: Omit<MotionStatesOptions, 'scope'>,
49
+ ): MotionStates<Extract<keyof S, string>, Extract<keyof T, string>>
50
+ /** Adopt an existing handle. Completed animations are released automatically. */
51
+ add<T extends MotionResource>(resource: T): T
52
+ /** Attach a subscription, detach on suspension, and recreate on resume. */
53
+ use(setup: () => MotionResource): () => void
54
+ /** Pause playable handles; cancel non-playable ones; detach subscriptions. */
55
+ suspend(): void
56
+ resume(): void
57
+ /** Cancel all owned work without destroying the scope, e.g. when recycling a cell. */
58
+ reset(): void
59
+ dispose(): void
60
+ }
61
+
62
+ /** Framework-independent ownership. Native unload never permanently destroys a scope. */
63
+ export function motionScope(): MotionScope {
64
+ const entries = new Set<Entry>()
65
+ const adopted = new Map<MotionResource, Entry>()
66
+ const identity = {}
67
+ let disposed = false
68
+ let suspended = false
69
+ let clearing = false
70
+ const assertAlive = () => {
71
+ if (disposed) throw new Error('Motion scope is disposed.')
72
+ if (clearing) throw new Error('Motion scope is resetting.')
73
+ }
74
+ const assertActive = () => {
75
+ assertAlive()
76
+ if (suspended)
77
+ throw new Error('Motion scope is suspended; resume it before starting work.')
78
+ }
79
+ const validate = (resource: MotionResource) => {
80
+ if (
81
+ typeof resource !== 'function' &&
82
+ (!resource ||
83
+ (typeof (resource as { dispose?: unknown }).dispose !== 'function' &&
84
+ typeof (resource as { stop?: unknown }).stop !== 'function'))
85
+ )
86
+ throw new TypeError(
87
+ 'A motion resource must be a cleanup function or expose stop()/dispose().',
88
+ )
89
+ const finished = (resource as Controlled).finished
90
+ if (finished && typeof finished.then !== 'function')
91
+ throw new TypeError('Resource finished must be a promise.')
92
+ if (owners.has(resource) && owners.get(resource) !== identity)
93
+ throw new Error('Motion resource already belongs to another scope.')
94
+ }
95
+ const forget = (entry: Entry) => {
96
+ const resource = entry.resource
97
+ if (resource) {
98
+ owners.delete(resource)
99
+ adopted.delete(resource)
100
+ entry.resource = undefined
101
+ }
102
+ entry.resume = false
103
+ }
104
+ const disposeResource = (resource: MotionResource) => {
105
+ if (typeof resource === 'function') resource()
106
+ else if (typeof (resource as { dispose?: unknown }).dispose === 'function')
107
+ (resource as { dispose(): void }).dispose()
108
+ else (resource as { stop(): void }).stop()
109
+ }
110
+ const release = (entry: Entry) => {
111
+ const resource = entry.resource
112
+ forget(entry)
113
+ if (!resource) return
114
+ disposeResource(resource)
115
+ }
116
+ const remove = (entry: Entry) => {
117
+ entries.delete(entry)
118
+ release(entry)
119
+ }
120
+ const attach = (entry: Entry, resource: MotionResource) => {
121
+ validate(resource)
122
+ if (adopted.has(resource))
123
+ throw new Error('Subscription factory returned an already owned resource.')
124
+ entry.resource = resource
125
+ adopted.set(resource, entry)
126
+ owners.set(resource, identity)
127
+ const finished = (resource as Controlled).finished
128
+ if (!entry.setup && finished) {
129
+ const settled = () => {
130
+ if (entry.resource !== resource) return
131
+ forget(entry)
132
+ entries.delete(entry)
133
+ }
134
+ void finished.then(settled, settled)
135
+ }
136
+ }
137
+ const startSubscription = (entry: Entry) => {
138
+ const resource = entry.setup!()
139
+ validate(resource)
140
+ // Setup may synchronously navigate away, suspend or remove its own scope.
141
+ if (disposed || suspended || !entries.has(entry)) {
142
+ if (!owners.has(resource)) disposeResource(resource)
143
+ return
144
+ }
145
+ attach(entry, resource)
146
+ }
147
+ // One bad cleanup must not strand the other subscriptions.
148
+ const visit = (action: (entry: Entry) => void) => {
149
+ const errors: unknown[] = []
150
+ for (const entry of [...entries]) {
151
+ if (!entries.has(entry)) continue
152
+ try {
153
+ action(entry)
154
+ } catch (error) {
155
+ errors.push(error)
156
+ }
157
+ }
158
+ if (errors.length)
159
+ throw new AggregateError(errors, 'Motion scope cleanup/lifecycle failed.')
160
+ }
161
+ const startOwned = <T extends MotionResource>(factory: () => T): T => {
162
+ assertActive()
163
+ const resource = factory()
164
+ try {
165
+ return scope.add(resource)
166
+ } catch (error) {
167
+ if (!owners.has(resource)) disposeResource(resource)
168
+ throw error
169
+ }
170
+ }
171
+ const scope: MotionScope = {
172
+ get disposed() {
173
+ return disposed
174
+ },
175
+ get suspended() {
176
+ return suspended
177
+ },
178
+ get size() {
179
+ return entries.size
180
+ },
181
+ animateLayout(
182
+ container: Target,
183
+ changeOrOptions?: (() => void) | LayoutOptions | SpringName,
184
+ options?: LayoutOptions | SpringName,
185
+ ) {
186
+ return startOwned(() =>
187
+ typeof changeOrOptions === 'function'
188
+ ? animateLayout(container, changeOrOptions, options)
189
+ : animateLayout(container, changeOrOptions),
190
+ )
191
+ },
192
+ animate(...args) {
193
+ return startOwned(() => animate(...args))
194
+ },
195
+ decay(...args) {
196
+ return startOwned(() => decay(...args))
197
+ },
198
+ sequence(...args) {
199
+ return startOwned(() => sequence(...args))
200
+ },
201
+ press(...args) {
202
+ return scope.use(() => press(...args))
203
+ },
204
+ drag(...args) {
205
+ return startOwned(() => drag(...args))
206
+ },
207
+ states(targets, definitions, options) {
208
+ return startOwned(() => motionStates(targets, definitions, options))
209
+ },
210
+ add(resource) {
211
+ assertActive()
212
+ validate(resource)
213
+ if (adopted.has(resource)) return resource
214
+ const entry: Entry = { resume: false }
215
+ attach(entry, resource)
216
+ entries.add(entry)
217
+ return resource
218
+ },
219
+ use(setup) {
220
+ assertAlive()
221
+ if (typeof setup !== 'function')
222
+ throw new TypeError('Motion subscription needs a factory.')
223
+ const entry: Entry = { setup, resume: false }
224
+ entries.add(entry)
225
+ try {
226
+ if (!suspended) startSubscription(entry)
227
+ } catch (error) {
228
+ remove(entry)
229
+ throw error
230
+ }
231
+ return () => remove(entry)
232
+ },
233
+ suspend() {
234
+ if (disposed || suspended) return
235
+ suspended = true
236
+ visit((entry) => {
237
+ if (entry.setup) {
238
+ release(entry)
239
+ return
240
+ }
241
+ const resource = entry.resource as Controlled | undefined
242
+ if (
243
+ typeof resource?.pause === 'function' &&
244
+ typeof resource.play === 'function'
245
+ ) {
246
+ entry.resume = resource.paused !== true
247
+ if (entry.resume) resource.pause()
248
+ } else remove(entry)
249
+ })
250
+ },
251
+ resume() {
252
+ if (disposed || !suspended) return
253
+ suspended = false
254
+ visit((entry) => {
255
+ if (disposed || suspended) return
256
+ if (entry.setup) startSubscription(entry)
257
+ else if (entry.resume) {
258
+ entry.resume = false
259
+ ;(entry.resource as Controlled).play?.()
260
+ }
261
+ })
262
+ },
263
+ reset() {
264
+ if (disposed || clearing) return
265
+ clearing = true
266
+ try {
267
+ visit(remove)
268
+ } finally {
269
+ clearing = false
270
+ }
271
+ },
272
+ dispose() {
273
+ if (disposed) return
274
+ disposed = true
275
+ visit(remove)
276
+ },
277
+ }
278
+ return scope
279
+ }
@@ -0,0 +1,269 @@
1
+ import { endingValues, prepareAnimation, set, toView } from './animate.js'
2
+ import { isColorProperty, type ColorValue } from './color.js'
3
+ import { isProperty } from './properties.js'
4
+ import type { Animation } from './engine.js'
5
+ import type { PlaybackAnimation } from './playback.js'
6
+ import type { KeyframeProperty, Keyframes, Target, Values } from './types.js'
7
+ import type { View } from '@nativescript/core'
8
+ import type { Spring, Tween, SpringName } from './transition.js'
9
+ import type { MotionScope } from './motion-scope.js'
10
+
11
+ /** Absolute endpoints only; keyframe arrays, paths and relative offsets are not states. */
12
+ export type MotionValues = {
13
+ [K in KeyframeProperty]?: K extends 'color' | 'backgroundColor' | 'borderColor'
14
+ ? ColorValue
15
+ : number
16
+ }
17
+ export type MotionState<K extends PropertyKey = string> = Partial<Record<K, MotionValues>>
18
+ /** Preserve inference while rejecting unknown target/property keys. */
19
+ export type MotionDefinitions<T, S> = S & {
20
+ [N in keyof S]: Record<Exclude<keyof S[N], keyof T>, never> & {
21
+ [K in keyof S[N]]: Record<Exclude<keyof S[N][K], KeyframeProperty>, never>
22
+ }
23
+ }
24
+ export type StateTransition =
25
+ | SpringName
26
+ | Omit<Spring, 'repeat' | 'repeatType'>
27
+ | Omit<Tween, 'repeat' | 'repeatType'>
28
+ export interface MotionStatesOptions {
29
+ transition?: StateTransition
30
+ scope?: MotionScope
31
+ }
32
+ export interface MotionStates<S extends string = string, T extends string = string> {
33
+ /** Latest requested state, not a claim that playback has finished. */
34
+ readonly current: S | undefined
35
+ readonly settled: S | undefined
36
+ readonly disposed: boolean
37
+ readonly paused: boolean
38
+ to(state: S, transition?: StateTransition): Animation
39
+ /**
40
+ * Point a target at a new view, or at none with `null`. The new view takes the current
41
+ * state at once, or joins the transition in progress from where it is.
42
+ */
43
+ attach(target: T, view: Target | null): void
44
+ /** A stable callback for a template ref: attaches the element and detaches it on unmount. */
45
+ ref(target: T): (view: Target | null) => void
46
+ pause(): void
47
+ play(): void
48
+ stop(): void
49
+ dispose(): void
50
+ }
51
+
52
+ /** Coordinated named targets. Omitted properties freeze when leaving their previous state. */
53
+ export function motionStates<
54
+ T extends Record<string, Target | null>,
55
+ const S extends Record<string, MotionState<keyof T>>,
56
+ >(
57
+ targets: T,
58
+ definitions: MotionDefinitions<T, S>,
59
+ options: MotionStatesOptions = {},
60
+ ): MotionStates<Extract<keyof S, string>, Extract<keyof T, string>> {
61
+ type Name = Extract<keyof S, string>
62
+ type Slot = Extract<keyof T, string>
63
+ const views = new Map<string, View | null>(
64
+ Object.entries(targets).map(([name, target]) => [
65
+ name,
66
+ target === null ? null : toView(target),
67
+ ]),
68
+ )
69
+ const assigned = [...views.values()].filter(Boolean)
70
+ if (new Set(assigned).size !== assigned.length)
71
+ throw new TypeError('Motion state targets must refer to distinct views.')
72
+ const states = new Map<string, [string, Keyframes][]>()
73
+ const defaults = normalize(options.transition)
74
+ for (const [name, state] of Object.entries(definitions)) {
75
+ if (!state || typeof state !== 'object' || Array.isArray(state))
76
+ throw new TypeError(`Invalid motion state "${name}".`)
77
+ const rows: [string, Keyframes][] = []
78
+ for (const [target, values] of Object.entries(state)) {
79
+ if (!views.has(target)) throw new TypeError(`Unknown motion target "${target}".`)
80
+ const view = views.get(target)
81
+ if (!values || typeof values !== 'object' || Array.isArray(values))
82
+ throw new TypeError(`Invalid motion values for "${target}".`)
83
+ const frames: Keyframes = {}
84
+ for (const [property, value] of Object.entries(values)) {
85
+ if (value === undefined) continue
86
+ if (property !== 'scale' && !isProperty(property))
87
+ throw new TypeError(`Invalid motion state property "${property}".`)
88
+ if (
89
+ !isColorProperty(property) &&
90
+ (typeof value !== 'number' || !Number.isFinite(value))
91
+ )
92
+ throw new TypeError(`Motion state "${property}" requires a finite endpoint.`)
93
+ if (Array.isArray(value) || value === null)
94
+ throw new TypeError(`Motion state "${property}" requires a single endpoint.`)
95
+ Object.assign(frames, { [property]: value })
96
+ }
97
+ // Validate every definition up front without claiming any native property.
98
+ if (view) prepareAnimation(view, frames, defaults)
99
+ else endingValues(frames, defaults)
100
+ rows.push([target, frames])
101
+ }
102
+ states.set(name, rows)
103
+ }
104
+ let current: Name | undefined
105
+ let settled: Name | undefined
106
+ let disposed = false
107
+ let paused = false
108
+ let active:
109
+ | { handle: Animation; children: PlaybackAnimation[]; timing: StateTransition }
110
+ | undefined
111
+ const refs = new Map<string, (view: Target | null) => void>()
112
+ const framesFor = (state: Name | undefined, target: string) =>
113
+ state === undefined
114
+ ? undefined
115
+ : states.get(state)!.find(([slot]) => slot === target)?.[1]
116
+ const controller: MotionStates<Name, Slot> = {
117
+ get current() {
118
+ return current
119
+ },
120
+ get settled() {
121
+ return settled
122
+ },
123
+ get disposed() {
124
+ return disposed
125
+ },
126
+ get paused() {
127
+ return paused
128
+ },
129
+ to(name, transition) {
130
+ if (disposed) throw new Error('Motion states controller is disposed.')
131
+ const rows = states.get(name)
132
+ if (!rows) throw new TypeError(`Unknown motion state "${name}".`)
133
+ const timing = transition === undefined ? defaults : normalize(transition)
134
+ const prepared = rows.flatMap(([target, frames]) => {
135
+ const view = views.get(target)
136
+ return view ? [prepareAnimation(view, frames, timing)] : []
137
+ })
138
+ const previous = active
139
+ const children: PlaybackAnimation[] = []
140
+ // Start replacements first: the engine transfers visible position AND velocity.
141
+ try {
142
+ for (const start of prepared) {
143
+ const child = start()
144
+ children.push(child)
145
+ if (paused) child.pause()
146
+ }
147
+ } catch (error) {
148
+ children.forEach((child) => child.stop())
149
+ previous?.handle.stop()
150
+ active = undefined
151
+ throw error
152
+ }
153
+ let cancelled = false
154
+ const finished = Promise.all(children.map((child) => child.finished)).then(
155
+ (results) => {
156
+ const completed =
157
+ !cancelled && !disposed && active?.handle === handle && results.every(Boolean)
158
+ if (active?.handle === handle) {
159
+ active = undefined
160
+ if (completed) settled = name
161
+ }
162
+ return completed
163
+ },
164
+ )
165
+ const handle: Animation = {
166
+ finished,
167
+ stop() {
168
+ cancelled = true
169
+ children.forEach((child) => child.stop())
170
+ },
171
+ then: (resolve, reject) => finished.then(resolve, reject),
172
+ }
173
+ active = { handle, children, timing }
174
+ current = name
175
+ // Also cancels old, omitted properties, without touching the new owners.
176
+ previous?.handle.stop()
177
+ return handle
178
+ },
179
+ attach(target, next) {
180
+ if (disposed) throw new Error('Motion states controller is disposed.')
181
+ if (!views.has(target)) throw new TypeError(`Unknown motion target "${target}".`)
182
+ const view = next === null ? null : toView(next)
183
+ if (views.get(target) === view) return
184
+ if (view)
185
+ for (const [other, assignedView] of views)
186
+ if (other !== target && assignedView === view)
187
+ throw new TypeError('Motion state targets must refer to distinct views.')
188
+ if (view)
189
+ for (const [, rows] of states)
190
+ for (const [slot, frames] of rows)
191
+ if (slot === target) prepareAnimation(view, frames, defaults)
192
+ views.set(target, view)
193
+ const frames = view ? framesFor(current, target) : undefined
194
+ if (!view || !frames) return
195
+ if (active) {
196
+ const child = prepareAnimation(view, frames, active.timing)()
197
+ active.children.push(child)
198
+ if (paused) child.pause()
199
+ } else set(view, frames as Values)
200
+ },
201
+ ref(target) {
202
+ let callback = refs.get(target)
203
+ if (!callback) {
204
+ callback = (view) => {
205
+ if (!disposed) controller.attach(target, view)
206
+ }
207
+ refs.set(target, callback)
208
+ }
209
+ return callback
210
+ },
211
+ pause() {
212
+ if (disposed || paused) return
213
+ paused = true
214
+ active?.children.forEach((child) => child.pause())
215
+ },
216
+ play() {
217
+ if (disposed || !paused) return
218
+ paused = false
219
+ active?.children.forEach((child) => child.play())
220
+ },
221
+ stop() {
222
+ active?.handle.stop()
223
+ active = undefined
224
+ },
225
+ dispose() {
226
+ if (disposed) return
227
+ disposed = true
228
+ controller.stop()
229
+ },
230
+ }
231
+ return options.scope ? options.scope.add(controller) : controller
232
+ }
233
+
234
+ function normalize(transition: StateTransition | undefined): StateTransition {
235
+ if (transition === undefined) return 'snappy'
236
+ if (typeof transition === 'string') {
237
+ if (!['smooth', 'snappy', 'bouncy'].includes(transition))
238
+ throw new TypeError('Unknown state spring.')
239
+ return transition
240
+ }
241
+ if (!transition || typeof transition !== 'object')
242
+ throw new TypeError('Invalid state transition.')
243
+ const allowed = new Set([
244
+ 'type',
245
+ 'delay',
246
+ 'duration',
247
+ 'ease',
248
+ 'times',
249
+ 'stiffness',
250
+ 'damping',
251
+ 'mass',
252
+ 'bounce',
253
+ 'velocity',
254
+ ])
255
+ for (const key of Object.keys(transition))
256
+ if (!allowed.has(key))
257
+ throw new TypeError(`State transitions do not support "${key}".`)
258
+ if (
259
+ transition.duration !== undefined &&
260
+ (!Number.isFinite(transition.duration) || transition.duration < 0)
261
+ )
262
+ throw new RangeError('State transition duration must be finite and nonnegative.')
263
+ if (
264
+ transition.delay !== undefined &&
265
+ (!Number.isFinite(transition.delay) || transition.delay < 0)
266
+ )
267
+ throw new RangeError('State transition delay must be finite and nonnegative.')
268
+ return { ...transition }
269
+ }