@rafaelaugusto/jolt 0.1.0-beta.3 → 0.1.0-beta.5

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 (132) hide show
  1. package/README.md +78 -13
  2. package/dist/animate.d.ts +3 -2
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +35 -5
  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/decoration.d.ts +18 -0
  11. package/dist/decoration.d.ts.map +1 -0
  12. package/dist/decoration.js +155 -0
  13. package/dist/decoration.js.map +1 -0
  14. package/dist/driver/android-color.d.ts +20 -0
  15. package/dist/driver/android-color.d.ts.map +1 -0
  16. package/dist/driver/android-color.js +136 -0
  17. package/dist/driver/android-color.js.map +1 -0
  18. package/dist/driver/android-decoration.d.ts +22 -0
  19. package/dist/driver/android-decoration.d.ts.map +1 -0
  20. package/dist/driver/android-decoration.js +139 -0
  21. package/dist/driver/android-decoration.js.map +1 -0
  22. package/dist/driver/android-playback.d.ts +13 -0
  23. package/dist/driver/android-playback.d.ts.map +1 -0
  24. package/dist/driver/android-playback.js +141 -0
  25. package/dist/driver/android-playback.js.map +1 -0
  26. package/dist/driver/android.d.ts.map +1 -1
  27. package/dist/driver/android.js +28 -35
  28. package/dist/driver/android.js.map +1 -1
  29. package/dist/driver/index.d.ts +8 -1
  30. package/dist/driver/index.d.ts.map +1 -1
  31. package/dist/driver/index.js.map +1 -1
  32. package/dist/driver/ios-color.d.ts +16 -0
  33. package/dist/driver/ios-color.d.ts.map +1 -0
  34. package/dist/driver/ios-color.js +392 -0
  35. package/dist/driver/ios-color.js.map +1 -0
  36. package/dist/driver/ios-decoration.d.ts +17 -0
  37. package/dist/driver/ios-decoration.d.ts.map +1 -0
  38. package/dist/driver/ios-decoration.js +271 -0
  39. package/dist/driver/ios-decoration.js.map +1 -0
  40. package/dist/driver/ios-playback.d.ts +10 -0
  41. package/dist/driver/ios-playback.d.ts.map +1 -0
  42. package/dist/driver/ios-playback.js +33 -0
  43. package/dist/driver/ios-playback.js.map +1 -0
  44. package/dist/driver/ios-shadow.d.ts +6 -0
  45. package/dist/driver/ios-shadow.d.ts.map +1 -0
  46. package/dist/driver/ios-shadow.js +52 -0
  47. package/dist/driver/ios-shadow.js.map +1 -0
  48. package/dist/driver/ios.d.ts.map +1 -1
  49. package/dist/driver/ios.js +78 -35
  50. package/dist/driver/ios.js.map +1 -1
  51. package/dist/engine.d.ts +48 -2
  52. package/dist/engine.d.ts.map +1 -1
  53. package/dist/engine.js +217 -21
  54. package/dist/engine.js.map +1 -1
  55. package/dist/haptics.d.ts +4 -1
  56. package/dist/haptics.d.ts.map +1 -1
  57. package/dist/haptics.js +27 -3
  58. package/dist/haptics.js.map +1 -1
  59. package/dist/index.d.ts +4 -0
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/liquid-grid.d.ts +3 -0
  64. package/dist/liquid-grid.d.ts.map +1 -0
  65. package/dist/liquid-grid.js +24 -0
  66. package/dist/liquid-grid.js.map +1 -0
  67. package/dist/liquid.d.ts +18 -0
  68. package/dist/liquid.d.ts.map +1 -0
  69. package/dist/liquid.js +229 -0
  70. package/dist/liquid.js.map +1 -0
  71. package/dist/playback-group.d.ts +6 -0
  72. package/dist/playback-group.d.ts.map +1 -0
  73. package/dist/playback-group.js +113 -0
  74. package/dist/playback-group.js.map +1 -0
  75. package/dist/playback.d.ts +25 -0
  76. package/dist/playback.d.ts.map +1 -0
  77. package/dist/playback.js +35 -0
  78. package/dist/playback.js.map +1 -0
  79. package/dist/press.d.ts.map +1 -1
  80. package/dist/press.js +6 -1
  81. package/dist/press.js.map +1 -1
  82. package/dist/properties.d.ts +5 -2
  83. package/dist/properties.d.ts.map +1 -1
  84. package/dist/properties.js +25 -1
  85. package/dist/properties.js.map +1 -1
  86. package/dist/sequence.d.ts.map +1 -1
  87. package/dist/sequence.js +6 -0
  88. package/dist/sequence.js.map +1 -1
  89. package/dist/text.d.ts.map +1 -1
  90. package/dist/text.js +3 -0
  91. package/dist/text.js.map +1 -1
  92. package/dist/track.d.ts +1 -1
  93. package/dist/track.d.ts.map +1 -1
  94. package/dist/track.js +48 -3
  95. package/dist/track.js.map +1 -1
  96. package/dist/transition.d.ts.map +1 -1
  97. package/dist/transition.js +11 -2
  98. package/dist/transition.js.map +1 -1
  99. package/dist/types.d.ts +20 -0
  100. package/dist/types.d.ts.map +1 -1
  101. package/package.json +11 -2
  102. package/platforms/android/include.gradle +7 -0
  103. package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
  104. package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
  105. package/platforms/android/proguard-rules.pro +7 -0
  106. package/src/animate.ts +48 -9
  107. package/src/color.ts +101 -0
  108. package/src/decoration.ts +180 -0
  109. package/src/driver/android-color.ts +170 -0
  110. package/src/driver/android-decoration.ts +182 -0
  111. package/src/driver/android-playback.ts +157 -0
  112. package/src/driver/android.ts +50 -46
  113. package/src/driver/index.ts +19 -1
  114. package/src/driver/ios-color.ts +441 -0
  115. package/src/driver/ios-decoration.ts +332 -0
  116. package/src/driver/ios-playback.ts +37 -0
  117. package/src/driver/ios-shadow.ts +50 -0
  118. package/src/driver/ios.ts +91 -42
  119. package/src/engine.ts +227 -23
  120. package/src/haptics.ts +25 -2
  121. package/src/index.ts +4 -0
  122. package/src/liquid-grid.ts +28 -0
  123. package/src/liquid.ts +282 -0
  124. package/src/playback-group.ts +110 -0
  125. package/src/playback.ts +62 -0
  126. package/src/press.ts +8 -2
  127. package/src/properties.ts +31 -5
  128. package/src/sequence.ts +5 -0
  129. package/src/text.ts +2 -0
  130. package/src/track.ts +72 -3
  131. package/src/transition.ts +13 -2
  132. package/src/types.ts +20 -0
package/src/engine.ts CHANGED
@@ -1,9 +1,17 @@
1
+ import {
2
+ createClock,
3
+ validateProgress,
4
+ type PlaybackAnimation,
5
+ type PlaybackClock,
6
+ type PlaybackCommand,
7
+ } from './playback.js'
1
8
  import type { View } from '@nativescript/core'
2
9
  import { getDriver } from './driver/index.js'
3
10
  import { playHaptics, type HapticOptions } from './haptics.js'
4
11
  import { readValue, writeValue, type Property } from './properties.js'
5
12
  import { createTrack, type Track } from './track.js'
6
13
  import type { ResolvedTransition } from './transition.js'
14
+ import { isColorProperty } from './color.js'
7
15
 
8
16
  export interface Animation extends PromiseLike<boolean> {
9
17
  /**
@@ -30,8 +38,14 @@ interface Entry {
30
38
  readonly owner: ViewAnimation
31
39
  handle: unknown
32
40
  done: boolean
41
+ finished: boolean
42
+ generation: number
43
+ token: symbol
44
+ completedValue?: number
33
45
  }
34
46
 
47
+ const revisions = new WeakMap<View, Map<Property, symbol>>()
48
+
35
49
  const running = new WeakMap<View, Map<Property, Entry>>()
36
50
  const arrivals = new WeakMap<Animation, Promise<unknown>>()
37
51
 
@@ -47,7 +61,7 @@ export function setArrival(animation: Animation, arrival: Promise<unknown>) {
47
61
  arrivals.set(animation, arrival)
48
62
  }
49
63
 
50
- export class ViewAnimation implements Animation {
64
+ export class ViewAnimation implements PlaybackAnimation {
51
65
  readonly finished: Promise<boolean>
52
66
  private resolve!: (finished: boolean) => void
53
67
  private readonly entries: Entry[] = []
@@ -58,12 +72,180 @@ export class ViewAnimation implements Animation {
58
72
  private arrive!: () => void
59
73
  private cancelArrival: (() => void) | undefined
60
74
  private haptics: ReturnType<typeof playHaptics>
75
+ private clock: PlaybackClock | undefined
76
+ private _paused = false
77
+ private controlled = false
78
+ private pendingProgress: number | undefined
79
+ private total = 0
80
+ private started = false
81
+ private arrival = 0
82
+ private arrived = false
83
+ private idleListener: (() => void) | undefined
84
+ private readyListener: (() => void) | undefined
85
+ private readonly endless: boolean
86
+
87
+ get paused() {
88
+ return this._paused
89
+ }
90
+ get duration() {
91
+ return this.started ? this.total : this.endless ? Infinity : 0
92
+ }
93
+ get currentTime() {
94
+ return this.time()
95
+ }
96
+ get ready() {
97
+ return this.started || this.settled
98
+ }
99
+ get idle() {
100
+ return this.settled || (this.started && this.pending === 0)
101
+ }
102
+ get progress() {
103
+ if (!Number.isFinite(this.duration))
104
+ throw new RangeError('Progress requires a finite animation.')
105
+ if (this.settled && this.complete) return 1
106
+ return (
107
+ this.pendingProgress ??
108
+ (this.duration ? Math.min(1, this.time() / this.duration) : this.settled ? 1 : 0)
109
+ )
110
+ }
111
+ set progress(value: number) {
112
+ validateProgress(value, this.duration)
113
+ if (this.settled) return
114
+ if (!this.started) {
115
+ this.pendingProgress = value
116
+ return
117
+ }
118
+ this.seekTime(this.duration * value)
119
+ }
120
+ onIdle(callback: () => void) {
121
+ this.idleListener = callback
122
+ }
123
+ onReady(callback: () => void) {
124
+ this.readyListener = callback
125
+ }
126
+ releaseGroup() {
127
+ this.held = false
128
+ if (this.idle && !this._paused) this.settle()
129
+ }
130
+
131
+ pause() {
132
+ if (this.settled || this._paused) return
133
+ this.controlled = true
134
+ this._paused = true
135
+ this.clock?.pause()
136
+ this.cancelArrival?.()
137
+ this.haptics?.pause()
138
+ if (this.started) this.controlTracks(this.time(), 'pause')
139
+ }
140
+ play() {
141
+ if (this.settled || !this._paused) return
142
+ this.controlled = true
143
+ this._paused = false
144
+ this.clock?.play()
145
+ if (this.started) {
146
+ this.controlTracks(this.time(), 'play')
147
+ this.scheduleArrival()
148
+ this.haptics?.play()
149
+ if (this.pending === 0) this.becameIdle()
150
+ }
151
+ }
152
+ seekTime(time: number) {
153
+ if (this.settled) return
154
+ this.controlled = true
155
+ const position = Math.max(0, Math.min(this.total, time))
156
+ this.clock?.seek(position)
157
+ if (!this._paused) this.clock?.play()
158
+ for (const entry of this.entries) {
159
+ if (
160
+ entry.done &&
161
+ entry.finished &&
162
+ revisions.get(this.view)?.get(entry.track.property) === entry.token &&
163
+ readValue(this.view, entry.track.property) === entry.completedValue
164
+ ) {
165
+ entry.done = false
166
+ entry.finished = false
167
+ this.pending++
168
+ entriesOf(this.view).set(entry.track.property, entry)
169
+ this.playEntry(entry, position)
170
+ }
171
+ }
172
+ this.controlTracks(position)
173
+ this.scheduleArrival()
174
+ this.haptics?.seek()
175
+ if (this.pending === 0) this.becameIdle()
176
+ }
177
+ elapsed(entry: Entry, now: number) {
178
+ return this.controlled
179
+ ? this.time()
180
+ : (getDriver().elapsed?.(entry.handle) ?? now - entry.startedAt)
181
+ }
182
+ private time() {
183
+ return this.clock?.time() ?? 0
184
+ }
185
+ private controlTracks(position: number, command: PlaybackCommand = 'seek') {
186
+ const driver = getDriver()
187
+ for (const entry of this.entries)
188
+ if (!entry.done)
189
+ driver.control?.(this.view, entry.handle, position, this._paused, command)
190
+ }
191
+ private scheduleArrival() {
192
+ this.cancelArrival?.()
193
+ this.cancelArrival = undefined
194
+ if (
195
+ this.pending === 0 ||
196
+ this.arrived ||
197
+ this._paused ||
198
+ !Number.isFinite(this.arrival) ||
199
+ this.total <= this.arrival
200
+ )
201
+ return
202
+ const remaining = this.arrival - this.time()
203
+ if (remaining <= 0) {
204
+ this.arrived = true
205
+ this.arrive()
206
+ } else
207
+ this.cancelArrival = getDriver().after(remaining, () => {
208
+ this.arrived = true
209
+ this.arrive()
210
+ })
211
+ }
212
+ private becameIdle() {
213
+ this.clock?.pause()
214
+ this.idleListener?.()
215
+ if (!this.complete || (!this.held && !this._paused)) this.settle()
216
+ }
217
+ private playEntry(entry: Entry, elapsed = 0) {
218
+ const initial = entry.generation === 0
219
+ const instance = ++entry.generation
220
+ const driver = getDriver()
221
+ entry.handle = driver.play(
222
+ this.view,
223
+ entry.track,
224
+ initial ? entry.startedAt : driver.now() - elapsed,
225
+ (finished = true) => {
226
+ if (entry.done || instance !== entry.generation) return
227
+ entry.done = true
228
+ entry.finished = finished
229
+ entry.completedValue = readValue(this.view, entry.track.property)
230
+ forget(entry)
231
+ this.ended(finished)
232
+ },
233
+ )
234
+ }
235
+ private unloaded = () => this.stop()
61
236
 
62
237
  constructor(
63
238
  readonly view: View,
64
239
  plans: readonly Plan[],
65
240
  private readonly haptic?: HapticOptions,
241
+ private held = false,
66
242
  ) {
243
+ this.endless = plans.some(
244
+ (plan) =>
245
+ !Number.isFinite(plan.transition.repeat) ||
246
+ (plan.transition.curve.type === 'tween' &&
247
+ !Number.isFinite(plan.transition.curve.duration)),
248
+ )
67
249
  this.finished = new Promise((resolve) => (this.resolve = resolve))
68
250
  setArrival(this, new Promise<void>((resolve) => (this.arrive = resolve)))
69
251
  if (view.isLoaded && view.nativeViewProtected) this.start(plans)
@@ -94,10 +276,11 @@ export class ViewAnimation implements Animation {
94
276
  this.complete = false
95
277
  this.haptics?.cancel()
96
278
  }
97
- if (--this.pending === 0) this.settle()
279
+ if (--this.pending === 0) this.becameIdle()
98
280
  }
99
281
 
100
282
  private start(plans: readonly Plan[]) {
283
+ this.started = true
101
284
  const driver = getDriver()
102
285
  const now = driver.now()
103
286
  const entries = entriesOf(this.view)
@@ -109,16 +292,19 @@ export class ViewAnimation implements Animation {
109
292
  if (previous) {
110
293
  const elapsed = elapsedOf(previous, now)
111
294
  current = driver.presented(this.view, property) ?? previous.track.valueAt(elapsed)
112
- velocity = previous.track.velocityAt(elapsed)
295
+ velocity = previous.owner.paused ? 0 : previous.track.velocityAt(elapsed)
113
296
  release(previous, now, false)
114
297
  } else {
115
- current = readValue(this.view, property)
298
+ current =
299
+ (isColorProperty(property)
300
+ ? driver.presented(this.view, property)
301
+ : undefined) ?? readValue(this.view, property)
116
302
  }
117
303
  return createTrack(
118
304
  property,
119
305
  keyframes.map((value) => {
120
306
  if (value === null) return current
121
- if (!relative) return value
307
+ if (!relative || isColorProperty(property)) return value
122
308
  return property === 'opacity' || property.startsWith('scale')
123
309
  ? current * value
124
310
  : current + value
@@ -129,8 +315,13 @@ export class ViewAnimation implements Animation {
129
315
  })
130
316
 
131
317
  for (const track of tracks) writeValue(this.view, track.property, track.to)
318
+ driver.sync?.(this.view)
132
319
 
133
320
  for (const track of tracks) {
321
+ let tokens = revisions.get(this.view)
322
+ if (!tokens) revisions.set(this.view, (tokens = new Map()))
323
+ const token = Symbol(track.property)
324
+ tokens.set(track.property, token)
134
325
  if (track.end === 0) continue
135
326
  const entry: Entry = {
136
327
  track,
@@ -138,33 +329,40 @@ export class ViewAnimation implements Animation {
138
329
  owner: this,
139
330
  handle: undefined,
140
331
  done: false,
332
+ finished: false,
333
+ generation: 0,
334
+ token,
141
335
  }
142
336
  entries.set(track.property, entry)
143
337
  this.entries.push(entry)
144
338
  this.pending++
145
339
  }
146
340
 
147
- this.haptics = playHaptics(this.view, tracks, this.haptic, driver, now)
148
-
149
- for (const entry of this.entries) {
150
- entry.handle = driver.play(this.view, entry.track, now, () => {
151
- if (entry.done) return
152
- entry.done = true
153
- forget(entry)
154
- this.ended(true)
155
- })
156
- }
157
-
158
- const arrival = Math.max(...this.entries.map((entry) => entry.track.arrival))
159
- if (this.pending === 0) this.settle()
160
- else if (Number.isFinite(arrival) && tracks.some((track) => track.end > arrival)) {
161
- this.cancelArrival = driver.after(arrival, this.arrive)
162
- }
341
+ this.total = Math.max(0, ...tracks.map((track) => track.end))
342
+ this.arrival = Math.max(0, ...tracks.map((track) => track.arrival))
343
+ this.clock =
344
+ (this.pending ? driver.clock?.(this.total) : undefined) ??
345
+ createClock(() => driver.now())
346
+ if (this._paused) this.clock.pause()
347
+ this.view.on('unloaded', this.unloaded)
348
+ this.haptics = playHaptics(this.view, tracks, this.haptic, driver, now, () =>
349
+ this.time(),
350
+ )
351
+ if (this._paused) this.haptics?.pause()
352
+ for (const entry of this.entries) this.playEntry(entry)
353
+ if (this.pendingProgress !== undefined) {
354
+ const progress = this.pendingProgress
355
+ this.pendingProgress = undefined
356
+ this.seekTime(progress * this.total)
357
+ } else if (this._paused) this.controlTracks(0, 'pause')
358
+ this.scheduleArrival()
359
+ this.readyListener?.()
360
+ if (this.pending === 0) this.becameIdle()
163
361
  }
164
362
 
165
363
  private waitForLoad(plans: readonly Plan[]) {
166
364
  for (const { property, keyframes, relative } of plans) {
167
- if (relative) continue
365
+ if (relative && !isColorProperty(property)) continue
168
366
  const first = keyframes[0]
169
367
  if (first !== null && first !== undefined) writeValue(this.view, property, first)
170
368
  }
@@ -183,6 +381,8 @@ export class ViewAnimation implements Animation {
183
381
  if (this.settled) return
184
382
  this.settled = true
185
383
  this.cancelArrival?.()
384
+ this.clock?.cancel()
385
+ this.view.off('unloaded', this.unloaded)
186
386
  if (this.complete) this.haptics?.finish()
187
387
  else this.haptics?.cancel()
188
388
  this.haptics = undefined
@@ -193,6 +393,9 @@ export class ViewAnimation implements Animation {
193
393
 
194
394
  /** Stops whatever is animating these properties of the view, or all of them. */
195
395
  export function stopView(view: View, properties?: readonly Property[]) {
396
+ const tokens = revisions.get(view)
397
+ for (const property of properties ?? tokens?.keys() ?? [])
398
+ tokens?.set(property, Symbol(property))
196
399
  const entries = running.get(view)
197
400
  if (!entries) return
198
401
  const now = getDriver().now()
@@ -222,11 +425,12 @@ function release(entry: Entry, now: number, commit: boolean) {
222
425
  const value =
223
426
  driver.presented(view, property) ?? entry.track.valueAt(elapsedOf(entry, now))
224
427
  writeValue(view, property, value)
428
+ driver.sync?.(view, property)
225
429
  }
226
430
  driver.stop(view, entry.handle)
227
431
  entry.owner.ended(false)
228
432
  }
229
433
 
230
434
  function elapsedOf(entry: Entry, now: number) {
231
- return getDriver().elapsed?.(entry.handle) ?? now - entry.startedAt
435
+ return entry.owner.elapsed(entry, now)
232
436
  }
package/src/haptics.ts CHANGED
@@ -48,6 +48,7 @@ export function playHaptics(
48
48
  options: HapticOptions | undefined,
49
49
  driver: Driver,
50
50
  startedAt: number,
51
+ elapsedTime = () => driver.now() - startedAt,
51
52
  ) {
52
53
  if (!isIOS || !options || typeof UIImpactFeedbackGenerator === 'undefined') return
53
54
  const moving = tracks.filter(
@@ -69,6 +70,7 @@ export function playHaptics(
69
70
  let generator: UIImpactFeedbackGenerator | undefined =
70
71
  UIImpactFeedbackGenerator.alloc().initWithStyle(styles[options.style])
71
72
  const native = view.nativeViewProtected as UIView
73
+ let paused = false
72
74
  let next = 0
73
75
  let cancelPrepare: (() => void) | undefined
74
76
  let cancelImpact: (() => void) | undefined
@@ -79,6 +81,11 @@ export function playHaptics(
79
81
  !!native.window &&
80
82
  UIApplication.sharedApplication.applicationState === 0 // UIApplicationState.Active
81
83
 
84
+ const cancelTimers = () => {
85
+ cancelPrepare?.()
86
+ cancelImpact?.()
87
+ cancelPrepare = cancelImpact = undefined
88
+ }
82
89
  const cancel = () => {
83
90
  cancelPrepare?.()
84
91
  cancelImpact?.()
@@ -88,7 +95,7 @@ export function playHaptics(
88
95
  }
89
96
 
90
97
  const fire = (tolerance = 0) => {
91
- const elapsed = driver.now() - startedAt
98
+ const elapsed = elapsedTime()
92
99
  // A busy UI thread can delay timers. Drop stale impacts instead of replaying
93
100
  // several old taps at once when it becomes responsive again.
94
101
  while (next < offsets.length && offsets[next]! <= elapsed + tolerance) {
@@ -98,8 +105,9 @@ export function playHaptics(
98
105
  }
99
106
 
100
107
  const schedule = () => {
108
+ if (paused) return
101
109
  if (!generator || next === offsets.length) return cancel()
102
- const remaining = startedAt + offsets[next]! - driver.now()
110
+ const remaining = offsets[next]! - elapsedTime()
103
111
  if (active()) generator.prepare()
104
112
  if (remaining > PREPARE_AHEAD) {
105
113
  cancelPrepare = driver.after(remaining - PREPARE_AHEAD, () => {
@@ -116,6 +124,21 @@ export function playHaptics(
116
124
  schedule()
117
125
  return {
118
126
  cancel,
127
+ pause() {
128
+ paused = true
129
+ cancelTimers()
130
+ },
131
+ play() {
132
+ if (paused) {
133
+ paused = false
134
+ schedule()
135
+ }
136
+ },
137
+ seek() {
138
+ cancelTimers()
139
+ while (next < offsets.length && offsets[next]! <= elapsedTime()) next++
140
+ schedule()
141
+ },
119
142
  finish() {
120
143
  // Native completion may beat the timer at the final keyframe by a frame.
121
144
  fire(COMPLETION_TOLERANCE)
package/src/index.ts CHANGED
@@ -7,6 +7,8 @@ export { confetti, particles } from './particles.js'
7
7
  export type { ConfettiOptions, ParticleOptions } from './particles.js'
8
8
  export { shimmer } from './shimmer.js'
9
9
  export type { ShimmerOptions } from './shimmer.js'
10
+ export { liquid } from './liquid.js'
11
+ export type { LiquidOptions, LiquidEffect } from './liquid.js'
10
12
  export { loader } from './loader.js'
11
13
  export type { LoaderOptions, LoaderStyle } from './loader.js'
12
14
  export { reveal, conceal } from './reveal.js'
@@ -25,5 +27,7 @@ export { springs } from './transition.js'
25
27
  export type { Spring, SpringName, Timing, Transition, Tween } from './transition.js'
26
28
  export { cubicBezier } from './easing.js'
27
29
  export type { CubicBezier, Easing, EasingFunction, EasingName } from './easing.js'
30
+ export type { PlaybackAnimation } from './playback.js'
28
31
  export type { Animation } from './engine.js'
32
+ export type { ColorValue, ColorKeyframes } from './color.js'
29
33
  export type * from './types.js'
@@ -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
+ }