@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
package/src/layout.ts ADDED
@@ -0,0 +1,408 @@
1
+ import { Application, type View } from '@nativescript/core'
2
+ import { prepareAnimation, toView } from './animate.js'
3
+ import { getDriver, type LayoutFrame } from './driver/index.js'
4
+ import {
5
+ captureValue,
6
+ propertyRevision,
7
+ arrivalOf,
8
+ setArrival,
9
+ type Animation,
10
+ } from './engine.js'
11
+ import { interactionSurface } from './interaction-surface.js'
12
+ import { readValue, writeValue, type NumericProperty } from './properties.js'
13
+ import { springs, type Spring, type Tween, type SpringName } from './transition.js'
14
+ import type { Keyframes, Target } from './types.js'
15
+
16
+ export type LayoutOptions = (
17
+ Omit<Spring, 'repeat' | 'repeatType'> | Omit<Tween, 'repeat' | 'repeatType'>
18
+ ) & {
19
+ /** Scale from the previous size (including content); false snaps size and only moves. Default: 'scale'. */
20
+ size?: 'scale' | false
21
+ /** Fade new children from zero to their configured opacity. Default: true. */
22
+ enter?: boolean
23
+ }
24
+
25
+ type Geometry = 'x' | 'y' | 'scaleX' | 'scaleY'
26
+ interface Snapshot {
27
+ frame: LayoutFrame
28
+ originX: number
29
+ originY: number
30
+ rotate: number
31
+ rotateX: number
32
+ rotateY: number
33
+ values: Record<Geometry, () => number | undefined>
34
+ }
35
+ interface Owned {
36
+ view: View
37
+ handle: Animation
38
+ detach(): void
39
+ targets: { property: NumericProperty; value: number; token: symbol | undefined }[]
40
+ }
41
+ const pending = new WeakMap<View, Animation>()
42
+ const playing = new WeakMap<View, Set<Animation>>()
43
+ const properties = ['x', 'y', 'scaleX', 'scaleY'] as const
44
+
45
+ /** Animate the next layout of a container's direct children. Call before a synchronous mutation. */
46
+ export function animateLayout(
47
+ container: Target,
48
+ options?: LayoutOptions | SpringName,
49
+ ): Animation
50
+ export function animateLayout(
51
+ container: Target,
52
+ change: () => void,
53
+ options?: LayoutOptions | SpringName,
54
+ ): Animation
55
+ export function animateLayout(
56
+ container: Target,
57
+ changeOrOptions?: (() => void) | LayoutOptions | SpringName,
58
+ options?: LayoutOptions | SpringName,
59
+ ): Animation {
60
+ const host = toView(container)
61
+ const change = typeof changeOrOptions === 'function' ? changeOrOptions : undefined
62
+ const input = change ? options : changeOrOptions
63
+ const {
64
+ size = 'scale',
65
+ enter = true,
66
+ ...transition
67
+ } = normalize(input as LayoutOptions | SpringName | undefined)
68
+ // Validate the complete transition before changing children or cancelling an earlier request.
69
+ prepareAnimation(
70
+ host,
71
+ { x: [0, 1], y: [0, 1], scaleX: [1, 2], scaleY: [1, 2], opacity: [0, 1] },
72
+ transition,
73
+ )
74
+ const driver = getDriver()
75
+ const before = new Map<View, Snapshot>()
76
+ const ready = laidOut(host)
77
+ if (ready)
78
+ for (const child of childrenOf(host)) {
79
+ const frame = frameOf(child)
80
+ if (frame)
81
+ before.set(child, {
82
+ frame,
83
+ originX: child.originX,
84
+ originY: child.originY,
85
+ rotate: child.rotate,
86
+ rotateX: child.rotateX,
87
+ rotateY: child.rotateY,
88
+ values: {
89
+ x: captureValue(child, 'x'),
90
+ y: captureValue(child, 'y'),
91
+ scaleX: captureValue(child, 'scaleX'),
92
+ scaleY: captureValue(child, 'scaleY'),
93
+ },
94
+ })
95
+ }
96
+
97
+ let resolve!: (finished: boolean) => void
98
+ let reject!: (error: unknown) => void
99
+ const finished = new Promise<boolean>((done, fail) => {
100
+ resolve = done
101
+ reject = fail
102
+ })
103
+ let arrive!: () => void
104
+ const arrival = new Promise<void>((done) => (arrive = done))
105
+ const owned: Owned[] = []
106
+ let settled = false
107
+ let detach: (() => void) | undefined
108
+ const cleanup = () => {
109
+ detach?.()
110
+ detach = undefined
111
+ host.off('unloaded', stop)
112
+ Application.off(Application.suspendEvent, stop)
113
+ if (pending.get(host) === handle) pending.delete(host)
114
+ const active = playing.get(host)
115
+ active?.delete(handle)
116
+ if (!active?.size) playing.delete(host)
117
+ before.clear()
118
+ owned.forEach((child) => child.detach())
119
+ arrive()
120
+ }
121
+ const complete = (result: boolean, error?: unknown) => {
122
+ if (settled) return
123
+ settled = true
124
+ cleanup()
125
+ if (error !== undefined) reject(error)
126
+ else resolve(result)
127
+ }
128
+ // A cancelled FLIP settles into the real layout rather than leaving a temporary inverse offset.
129
+ // The engine marker protects any animation that took ownership after this operation.
130
+ const restoreOne = ({ view, handle: child, targets }: Owned) => {
131
+ const current = targets.filter(
132
+ (target) => propertyRevision(view, target.property) === target.token,
133
+ )
134
+ child.stop()
135
+ for (const target of current)
136
+ if (propertyRevision(view, target.property) === target.token)
137
+ writeValue(view, target.property, target.value)
138
+ if (current.length) driver.sync?.(view)
139
+ }
140
+ const restore = () => {
141
+ owned.forEach(restoreOne)
142
+ }
143
+ function stop() {
144
+ if (settled) return
145
+ restore()
146
+ complete(false)
147
+ }
148
+ const handle: Animation = {
149
+ finished,
150
+ stop,
151
+ then: (onfulfilled, onrejected) => finished.then(onfulfilled, onrejected),
152
+ }
153
+ setArrival(handle, arrival)
154
+ const run = () => {
155
+ if (settled) return
156
+ detach?.()
157
+ detach = undefined
158
+ if (!laidOut(host)) {
159
+ stop()
160
+ return
161
+ }
162
+ if (pending.get(host) === handle) pending.delete(host)
163
+ const previous = [...(playing.get(host) ?? [])]
164
+ try {
165
+ const prepared: { view: View; frames: Keyframes; start: () => Animation }[] = []
166
+ for (const child of childrenOf(host)) {
167
+ const now = frameOf(child)
168
+ if (!now) continue
169
+ const was = before.get(child)
170
+ const frames = was
171
+ ? inverse(child, was, now, size)
172
+ : enter
173
+ ? { opacity: [0, child.opacity] }
174
+ : undefined
175
+ if (frames && Object.keys(frames).length)
176
+ prepared.push({
177
+ view: child,
178
+ frames,
179
+ start: prepareAnimation(child, frames, transition),
180
+ })
181
+ }
182
+ before.clear()
183
+ // Selection lasts only for starting these tracks; their native geometry driver stays until completion.
184
+ for (const { view, frames, start } of prepared) {
185
+ const surface = interactionSurface(view)
186
+ try {
187
+ const child = start()
188
+ const targets = Object.entries(frames).map(([property, frames]) => ({
189
+ property: property as NumericProperty,
190
+ value: (frames as number[]).at(-1)!,
191
+ token: propertyRevision(view, property as NumericProperty),
192
+ }))
193
+ const item: Owned = {
194
+ view,
195
+ handle: child,
196
+ targets,
197
+ detach: () => view.off('unloaded', unloaded),
198
+ }
199
+ const unloaded = () => {
200
+ item.detach()
201
+ if (!settled) restoreOne(item)
202
+ }
203
+ // Keyed reorder may unload/reinsert the same native view. Do not persist its FLIP offset.
204
+ view.on('unloaded', unloaded)
205
+ owned.push(item)
206
+ } finally {
207
+ surface.dispose()
208
+ }
209
+ }
210
+ const active = playing.get(host) ?? new Set<Animation>()
211
+ active.add(handle)
212
+ playing.set(host, active)
213
+ // Replacements start before old handles stop so the engine can transfer velocity.
214
+ for (const old of previous) old.stop()
215
+ void Promise.all(owned.map(({ handle }) => arrivalOf(handle))).then(arrive)
216
+ void Promise.all(owned.map(({ handle }) => handle.finished)).then(
217
+ (results) => complete(results.every(Boolean)),
218
+ (error) => {
219
+ restore()
220
+ complete(false, error)
221
+ },
222
+ )
223
+ } catch (error) {
224
+ restore()
225
+ complete(false, error)
226
+ }
227
+ }
228
+
229
+ pending.get(host)?.stop()
230
+ pending.set(host, handle)
231
+ host.on('unloaded', stop)
232
+ Application.on(Application.suspendEvent, stop)
233
+ try {
234
+ const result = change?.() as unknown
235
+ if (result && typeof (result as PromiseLike<unknown>).then === 'function') {
236
+ void Promise.resolve(result).catch(() => {})
237
+ throw new TypeError(
238
+ 'Layout mutations must be synchronous. Call animateLayout after awaited work.',
239
+ )
240
+ }
241
+ if (!settled) {
242
+ if (!ready || !laidOut(host)) complete(true)
243
+ else {
244
+ if (!driver.afterLayout || !driver.layoutFrame)
245
+ throw new Error('The animation driver does not support layout changes.')
246
+ detach = driver.afterLayout(host, run)
247
+ // Always request a pass, including a no-op mutation: no arbitrary timer or hanging wait.
248
+ host.requestLayout()
249
+ }
250
+ }
251
+ } catch (error) {
252
+ stop()
253
+ throw error
254
+ }
255
+ return handle
256
+ }
257
+
258
+ function normalize(input: LayoutOptions | SpringName | undefined): LayoutOptions {
259
+ if (typeof input === 'string') {
260
+ if (!Object.hasOwn(springs, input))
261
+ throw new TypeError(`Unknown layout spring "${input}".`)
262
+ return normalize({ ...springs[input] })
263
+ }
264
+ if (input === undefined) return {}
265
+ if (!input || typeof input !== 'object' || Array.isArray(input))
266
+ throw new TypeError('Invalid layout options.')
267
+ const allowed = new Set([
268
+ 'type',
269
+ 'duration',
270
+ 'ease',
271
+ 'times',
272
+ 'stiffness',
273
+ 'damping',
274
+ 'mass',
275
+ 'bounce',
276
+ 'velocity',
277
+ 'delay',
278
+ 'size',
279
+ 'enter',
280
+ ])
281
+ for (const key of Object.keys(input))
282
+ if (!allowed.has(key)) throw new TypeError(`Unsupported layout option "${key}".`)
283
+ if (input.size !== undefined && input.size !== false && input.size !== 'scale')
284
+ throw new TypeError('Layout size must be "scale" or false.')
285
+ if (input.enter !== undefined && typeof input.enter !== 'boolean')
286
+ throw new TypeError('Layout enter must be boolean.')
287
+ if (input.type !== undefined && input.type !== 'spring' && input.type !== 'tween')
288
+ throw new TypeError('Layout type must be "spring" or "tween".')
289
+ const numbers = input as Record<string, unknown>
290
+ for (const key of [
291
+ 'duration',
292
+ 'delay',
293
+ 'stiffness',
294
+ 'damping',
295
+ 'mass',
296
+ 'bounce',
297
+ 'velocity',
298
+ ]) {
299
+ const value = numbers[key]
300
+ if (value !== undefined && (typeof value !== 'number' || !Number.isFinite(value)))
301
+ throw new RangeError(`Layout ${key} must be finite.`)
302
+ if (typeof value === 'number') {
303
+ if (['duration', 'delay', 'damping'].includes(key) && value < 0)
304
+ throw new RangeError(`Layout ${key} must be nonnegative.`)
305
+ if (['stiffness', 'mass'].includes(key) && value <= 0)
306
+ throw new RangeError(`Layout ${key} must be positive.`)
307
+ if (key === 'bounce' && (value < -1 || value > 1))
308
+ throw new RangeError('Layout bounce must be between -1 and 1.')
309
+ }
310
+ }
311
+ const physical = ['stiffness', 'damping', 'mass'].some(
312
+ (key) => numbers[key] !== undefined,
313
+ )
314
+ const spring =
315
+ input.type === 'spring' ||
316
+ (input.type !== 'tween' &&
317
+ ['bounce', 'velocity', 'stiffness', 'damping', 'mass'].some(
318
+ (key) => numbers[key] !== undefined,
319
+ ))
320
+ if (spring && !physical && input.duration === 0)
321
+ throw new RangeError(
322
+ 'A duration-based layout spring must have positive duration; use a zero-duration tween to settle immediately.',
323
+ )
324
+ const result = { ...input }
325
+ if ('ease' in result && Array.isArray(result.ease)) result.ease = [...result.ease]
326
+ if ('times' in result && result.times) result.times = [...result.times]
327
+ return result
328
+ }
329
+
330
+ function inverse(
331
+ view: View,
332
+ was: Snapshot,
333
+ now: LayoutFrame,
334
+ size: LayoutOptions['size'],
335
+ ): Keyframes | undefined {
336
+ // Origin/rotation changes require a different affine transform, not a translation/scale FLIP.
337
+ if (
338
+ view.originX !== was.originX ||
339
+ view.originY !== was.originY ||
340
+ view.rotate !== was.rotate ||
341
+ was.rotateX ||
342
+ was.rotateY ||
343
+ view.rotateX ||
344
+ view.rotateY
345
+ )
346
+ return undefined
347
+ const frames: Keyframes = {}
348
+ const width = was.frame.width - now.width,
349
+ height = was.frame.height - now.height
350
+ const resized = Math.abs(width) > 0.25 || Math.abs(height) > 0.25
351
+ for (const property of properties) {
352
+ const current = was.values[property]()
353
+ if (current === undefined) continue // A newer explicit animation owns this property.
354
+ let start: number
355
+ if (property === 'x')
356
+ start =
357
+ was.frame.x -
358
+ now.x +
359
+ current +
360
+ (size === 'scale' && resized ? width * was.originX : 0)
361
+ else if (property === 'y')
362
+ start =
363
+ was.frame.y -
364
+ now.y +
365
+ current +
366
+ (size === 'scale' && resized ? height * was.originY : 0)
367
+ else {
368
+ if (size !== 'scale') continue
369
+ start =
370
+ current *
371
+ (property === 'scaleX'
372
+ ? was.frame.width / now.width
373
+ : was.frame.height / now.height)
374
+ }
375
+ const end = readValue(view, property)
376
+ if (Math.abs(start - end) > (property.startsWith('scale') ? 0.0001 : 0.25))
377
+ frames[property] = [start, end]
378
+ }
379
+ return frames
380
+ }
381
+ function laidOut(view: View): boolean {
382
+ if (!view.isLoaded || !view.nativeViewProtected) return false
383
+ for (
384
+ let current: View | undefined = view;
385
+ current;
386
+ current = current.parent as View | undefined
387
+ )
388
+ if (current.isCollapsed) return false
389
+ return true
390
+ }
391
+ function frameOf(view: View): LayoutFrame | undefined {
392
+ if (!laidOut(view)) return undefined
393
+ const frame = getDriver().layoutFrame?.(view)
394
+ return frame &&
395
+ Object.values(frame).every(Number.isFinite) &&
396
+ frame.width > 0 &&
397
+ frame.height > 0
398
+ ? frame
399
+ : undefined
400
+ }
401
+ function childrenOf(host: View): View[] {
402
+ const children: View[] = []
403
+ host.eachChildView((child) => {
404
+ children.push(child)
405
+ return true
406
+ })
407
+ return children
408
+ }
@@ -0,0 +1,28 @@
1
+ /** A periodic displacement field. The fixed boundary keeps transparent edges in bounds. */
2
+ export function liquidGrid(
3
+ columns: number,
4
+ rows: number,
5
+ phase: number,
6
+ intensity: number,
7
+ ) {
8
+ const positions = new Float32Array((columns + 1) * (rows + 1) * 2)
9
+ const t = phase * Math.PI * 2
10
+ let index = 0
11
+ for (let row = 0; row <= rows; row++) {
12
+ const y = row / rows
13
+ for (let column = 0; column <= columns; column++) {
14
+ const x = column / columns
15
+ const edge = Math.sin(Math.PI * x) ** 2 * Math.sin(Math.PI * y) ** 2
16
+ const amount = 0.055 * intensity * edge
17
+ positions[index++] =
18
+ x +
19
+ amount *
20
+ (Math.sin(Math.PI * 2 * y + t) + 0.4 * Math.sin(Math.PI * 2 * (x + y) - 2 * t))
21
+ positions[index++] =
22
+ y +
23
+ amount *
24
+ (Math.cos(Math.PI * 2 * x - t) + 0.4 * Math.cos(Math.PI * 2 * (x - y) + 2 * t))
25
+ }
26
+ }
27
+ return positions
28
+ }