@rafaelaugusto/jolt 0.1.0-beta.11 → 0.1.0-beta.12

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 (79) hide show
  1. package/README.md +38 -7
  2. package/dist/animate.d.ts +14 -4
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +50 -34
  5. package/dist/animate.js.map +1 -1
  6. package/dist/drag.d.ts +17 -3
  7. package/dist/drag.d.ts.map +1 -1
  8. package/dist/drag.js +34 -11
  9. package/dist/drag.js.map +1 -1
  10. package/dist/easing.d.ts +1 -1
  11. package/dist/easing.d.ts.map +1 -1
  12. package/dist/easing.js +11 -0
  13. package/dist/easing.js.map +1 -1
  14. package/dist/engine.d.ts +18 -1
  15. package/dist/engine.d.ts.map +1 -1
  16. package/dist/engine.js +143 -36
  17. package/dist/engine.js.map +1 -1
  18. package/dist/gradient.d.ts.map +1 -1
  19. package/dist/gradient.js +10 -4
  20. package/dist/gradient.js.map +1 -1
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/keyboard.d.ts.map +1 -1
  26. package/dist/keyboard.js +63 -10
  27. package/dist/keyboard.js.map +1 -1
  28. package/dist/mixer.d.ts +49 -0
  29. package/dist/mixer.d.ts.map +1 -0
  30. package/dist/mixer.js +238 -0
  31. package/dist/mixer.js.map +1 -0
  32. package/dist/motion-scope.d.ts +4 -1
  33. package/dist/motion-scope.d.ts.map +1 -1
  34. package/dist/motion-scope.js +8 -3
  35. package/dist/motion-scope.js.map +1 -1
  36. package/dist/motion-states.d.ts +10 -5
  37. package/dist/motion-states.d.ts.map +1 -1
  38. package/dist/motion-states.js +55 -22
  39. package/dist/motion-states.js.map +1 -1
  40. package/dist/motion-value.d.ts +115 -0
  41. package/dist/motion-value.d.ts.map +1 -0
  42. package/dist/motion-value.js +437 -0
  43. package/dist/motion-value.js.map +1 -0
  44. package/dist/path.d.ts.map +1 -1
  45. package/dist/path.js +2 -2
  46. package/dist/path.js.map +1 -1
  47. package/dist/track.d.ts.map +1 -1
  48. package/dist/track.js +31 -12
  49. package/dist/track.js.map +1 -1
  50. package/dist/transition.d.ts +15 -4
  51. package/dist/transition.d.ts.map +1 -1
  52. package/dist/transition.js +46 -7
  53. package/dist/transition.js.map +1 -1
  54. package/dist/types.d.ts +5 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/vue/index.d.ts +28 -2
  57. package/dist/vue/index.d.ts.map +1 -1
  58. package/dist/vue/index.js +78 -2
  59. package/dist/vue/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrame.java +53 -0
  62. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrameFollower.java +57 -0
  63. package/src/android-plugin.d.ts +27 -0
  64. package/src/animate.ts +99 -16
  65. package/src/drag.ts +67 -21
  66. package/src/easing.ts +14 -0
  67. package/src/engine.ts +158 -53
  68. package/src/gradient.ts +10 -4
  69. package/src/index.ts +18 -1
  70. package/src/keyboard.ts +83 -18
  71. package/src/mixer.ts +264 -0
  72. package/src/motion-scope.ts +12 -4
  73. package/src/motion-states.ts +79 -29
  74. package/src/motion-value.ts +563 -0
  75. package/src/path.ts +2 -1
  76. package/src/track.ts +37 -12
  77. package/src/transition.ts +69 -10
  78. package/src/types.ts +5 -0
  79. package/src/vue/index.ts +98 -1
@@ -136,6 +136,33 @@ declare namespace org.rafaelaugusto.jolt {
136
136
  })
137
137
  }
138
138
  }
139
+ class KeyboardFrame {
140
+ constructor(root: android.view.View, listener: KeyboardFrame.Listener)
141
+ attach(): void
142
+ detach(): void
143
+ height(): number
144
+ }
145
+ namespace KeyboardFrame {
146
+ class Listener {
147
+ constructor(implementation: { onChange(height: number): void })
148
+ }
149
+ }
150
+ class KeyboardFrameFollower {
151
+ constructor(
152
+ view: android.view.View,
153
+ offset: number,
154
+ listener: KeyboardFrameFollower.Listener,
155
+ )
156
+ attach(): void
157
+ detach(): void
158
+ }
159
+ namespace KeyboardFrameFollower {
160
+ class Listener {
161
+ constructor(implementation: {
162
+ onMove(from: number, to: number, keyboard: number): void
163
+ })
164
+ }
165
+ }
139
166
  class TextReveal {
140
167
  constructor(
141
168
  view: android.widget.TextView,
package/src/animate.ts CHANGED
@@ -17,16 +17,25 @@ import { DEFAULT_FRICTION, solveDecay } from './decay.js'
17
17
  import {
18
18
  arrivalOf,
19
19
  setArrival,
20
+ stopLayer,
20
21
  stopView,
21
22
  ViewAnimation,
22
23
  type Animation,
23
24
  type Plan,
24
25
  } from './engine.js'
25
26
  import { getPreset, type PresetName } from './presets.js'
27
+ import {
28
+ asRoot,
29
+ detachBindings,
30
+ isMotionValue,
31
+ type MotionValue,
32
+ } from './motion-value.js'
26
33
  import { resolveHaptic, type Haptic } from './haptics.js'
34
+ import { layerable } from './mixer.js'
27
35
  import { isProperty, precision, type Property } from './properties.js'
28
36
  import {
29
37
  hasCurve,
38
+ fitTransition,
30
39
  mergeTransitions,
31
40
  resolveTransition,
32
41
  springs,
@@ -51,24 +60,45 @@ interface Options {
51
60
  stagger: number | Stagger | undefined
52
61
  relative: boolean | undefined
53
62
  haptic: Haptic | undefined
63
+ layer?: string
54
64
  }
55
65
 
56
66
  /**
57
67
  * Animates one view, or several. Takes keyframes or the name of a preset,
58
- * and a transition or the name of a spring.
68
+ * and a transition or the name of a spring. A motion value animates to a number.
59
69
  *
60
70
  * ```ts
61
71
  * animate(card, 'fadeInUp')
62
72
  * animate(card, { scale: 1.1, rotate: [0, 90] }, 'bouncy')
63
73
  * animate(rows, 'fadeInUp', { stagger: 40 })
74
+ * animate(progress, 1, 'snappy')
64
75
  * ```
65
76
  */
77
+ export function animate(
78
+ value: MotionValue,
79
+ to: number | readonly (number | null)[],
80
+ transition?: Transition | SpringName,
81
+ ): Animation
66
82
  export function animate(
67
83
  target: Target | readonly Target[],
68
84
  animation: Keyframes | PresetName,
69
85
  options?: AnimateOptions | SpringName,
70
- ): PlaybackAnimation {
71
- return prepareAnimation(target, animation, options)()
86
+ ): PlaybackAnimation
87
+ export function animate(
88
+ target: Target | readonly Target[] | MotionValue,
89
+ animation: Keyframes | PresetName | number | readonly (number | null)[],
90
+ options?: AnimateOptions | SpringName,
91
+ ): PlaybackAnimation | Animation {
92
+ if (isMotionValue(target))
93
+ return asRoot(target).animate(
94
+ animation as number | readonly (number | null)[],
95
+ options as Transition | SpringName | undefined,
96
+ )
97
+ return prepareAnimation(
98
+ target as Target | readonly Target[],
99
+ animation as Keyframes | PresetName,
100
+ options,
101
+ )()
72
102
  }
73
103
 
74
104
  /** Validate and snapshot a complete animation before changing any view ownership. */
@@ -77,7 +107,7 @@ export function prepareAnimation(
77
107
  animation: Keyframes | PresetName,
78
108
  options?: AnimateOptions | SpringName,
79
109
  ): () => PlaybackAnimation {
80
- const { keyframes, base, overrides, stagger, relative, haptic, path } = prepare(
110
+ const { keyframes, base, overrides, stagger, relative, haptic, path, layer } = prepare(
81
111
  animation,
82
112
  options,
83
113
  )
@@ -88,8 +118,15 @@ export function prepareAnimation(
88
118
  for (const property of Object.keys(keyframes))
89
119
  if (isColorProperty(property)) validateColorTarget(view, property)
90
120
  else if (isDecorationProperty(property)) validateDecorationTarget(view, property)
91
- const plans = plan(keyframes, base, overrides, 0, relative, path)
92
- return () => new ViewAnimation(view, plans, feedback)
121
+ const plans = plan(keyframes, base, overrides, 0, relative, path, layer)
122
+ return () => {
123
+ if (layer === undefined)
124
+ detachBindings(
125
+ view,
126
+ plans.map(({ property }) => property),
127
+ )
128
+ return new ViewAnimation(view, plans, feedback)
129
+ }
93
130
  }
94
131
 
95
132
  const views = target.map(toView)
@@ -97,6 +134,7 @@ export function prepareAnimation(
97
134
  for (const property of Object.keys(keyframes))
98
135
  if (isColorProperty(property)) validateColorTarget(view, property)
99
136
  else if (isDecorationProperty(property)) validateDecorationTarget(view, property)
137
+ const seed = Math.random()
100
138
  const plans = views.map((_, index) =>
101
139
  plan(
102
140
  keyframes,
@@ -105,11 +143,20 @@ export function prepareAnimation(
105
143
  staggerDelay(stagger, index, views.length),
106
144
  relative,
107
145
  path,
146
+ layer,
147
+ seed,
108
148
  ),
109
149
  )
110
150
  return () =>
111
151
  playbackGroup(
112
- views.map((view, index) => new ViewAnimation(view, plans[index]!, feedback, true)),
152
+ views.map((view, index) => {
153
+ if (layer === undefined)
154
+ detachBindings(
155
+ view,
156
+ plans[index]!.map(({ property }) => property),
157
+ )
158
+ return new ViewAnimation(view, plans[index]!, feedback, true)
159
+ }),
113
160
  )
114
161
  }
115
162
 
@@ -186,15 +233,28 @@ export function decay(
186
233
  )
187
234
  }
188
235
 
189
- /** Stops all or selected properties at their current values. `scale` stops both axes. */
236
+ /**
237
+ * Stops all or selected properties at their current values. `scale` stops both axes.
238
+ * A motion value stops where it is, and so do the views bound to it.
239
+ */
240
+ export function stop(value: MotionValue): void
190
241
  export function stop(
191
242
  target: Target | readonly Target[],
192
243
  properties?: readonly KeyframeProperty[],
244
+ options?: { layer?: string },
245
+ ): void
246
+ export function stop(
247
+ target: Target | readonly Target[] | MotionValue,
248
+ properties?: readonly KeyframeProperty[],
249
+ options?: { layer?: string },
193
250
  ) {
251
+ if (isMotionValue(target)) return asRoot(target).stop()
194
252
  const expanded = properties?.flatMap<Property>((property) =>
195
253
  property === 'scale' ? ['scaleX', 'scaleY'] : [property],
196
254
  )
197
- for (const view of toViews(target)) stopView(view, expanded)
255
+ for (const view of toViews(target as Target | readonly Target[]))
256
+ if (options?.layer !== undefined) stopLayer(view, options.layer, expanded)
257
+ else stopView(view, expanded)
198
258
  }
199
259
 
200
260
  /**
@@ -205,7 +265,13 @@ export function stop(
205
265
  * set(logo, { opacity: 1, scale: 1, y: 0 })
206
266
  * ```
207
267
  */
208
- export function set(target: Target | readonly Target[], values: Values): void {
268
+ export function set(value: MotionValue, to: number): void
269
+ export function set(target: Target | readonly Target[], values: Values): void
270
+ export function set(
271
+ target: Target | readonly Target[] | MotionValue,
272
+ values: Values | number,
273
+ ) {
274
+ if (isMotionValue(target)) return asRoot(target).set(values as number)
209
275
  if (values === null || typeof values !== 'object' || Array.isArray(values))
210
276
  throw new TypeError('set() takes an object of values.')
211
277
  const keyframes: Record<string, unknown> = {}
@@ -218,7 +284,7 @@ export function set(target: Target | readonly Target[], values: Values): void {
218
284
  )
219
285
  keyframes[key] = [value, value]
220
286
  }
221
- animate(target, keyframes as Keyframes, { duration: 0 })
287
+ animate(target as Target | readonly Target[], keyframes as Keyframes, { duration: 0 })
222
288
  }
223
289
 
224
290
  /** Plays several animations as one: finished once they all are, and stopped together. */
@@ -281,6 +347,7 @@ function resolveAnimation(
281
347
  stagger: user.stagger ?? own.stagger,
282
348
  relative: user.relative ?? own.relative,
283
349
  haptic: user.haptic ?? own.haptic,
350
+ layer: user.layer,
284
351
  }
285
352
  }
286
353
 
@@ -307,16 +374,21 @@ function normalize(options: AnimateOptions | SpringName | undefined): Options {
307
374
  let stagger: Options['stagger']
308
375
  let relative: Options['relative']
309
376
  let haptic: Options['haptic']
377
+ let layer: Options['layer']
310
378
  for (const [key, value] of Object.entries(options)) {
311
379
  if (value === undefined) continue
312
- if (key === 'stagger') stagger = value as Options['stagger']
380
+ if (key === 'layer') {
381
+ if (typeof value !== 'string' || !value)
382
+ throw new TypeError('A layer is named by a nonempty string.')
383
+ layer = value
384
+ } else if (key === 'stagger') stagger = value as Options['stagger']
313
385
  else if (key === 'relative') relative = value as Options['relative']
314
386
  else if (key === 'haptic') haptic = value as Options['haptic']
315
387
  else if (key === 'path' || key === 'scale' || isProperty(key)) {
316
388
  overrides[key] = typeof value === 'string' ? spring(value as SpringName) : value
317
389
  } else base[key] = value
318
390
  }
319
- return { base, overrides, stagger, relative, haptic }
391
+ return { base, overrides, stagger, relative, haptic, layer }
320
392
  }
321
393
 
322
394
  function plan(
@@ -326,8 +398,16 @@ function plan(
326
398
  offset: number,
327
399
  relative = false,
328
400
  path?: PreparedPath,
401
+ layer?: string,
402
+ seed = Math.random(),
329
403
  ): Plan[] {
404
+ if (layer !== undefined && path)
405
+ throw new Error("A path owns x and y; it can't play on a layer.")
330
406
  return expand(keyframes, path).map(([property, frames]) => {
407
+ if (layer !== undefined && !layerable(property))
408
+ throw new Error(
409
+ `Layers apply to x, y, scale, rotation and opacity; "${property}" has a single owner.`,
410
+ )
331
411
  if (path?.properties.includes(property as 'x' | 'y' | 'rotate')) {
332
412
  if (
333
413
  !Number.isFinite(path.transition.delay + offset) ||
@@ -347,12 +427,13 @@ function plan(
347
427
  const override =
348
428
  overrides[property] ?? (property.startsWith('scale') ? overrides.scale : undefined)
349
429
  const transition = resolveTransition(
350
- override ? mergeTransitions(base, override) : base,
430
+ fitTransition(override ? mergeTransitions(base, override) : base, frames.length),
351
431
  property,
352
432
  frames.length,
353
433
  )
354
434
  transition.delay += offset
355
- return { property, keyframes: frames, transition, relative }
435
+ if (transition.seed !== undefined) transition.seed = seed
436
+ return { property, keyframes: frames, transition, relative, layer }
356
437
  })
357
438
  }
358
439
 
@@ -361,8 +442,10 @@ export function startingValues(
361
442
  animation: Keyframes | PresetName,
362
443
  options?: AnimateOptions | SpringName,
363
444
  ) {
364
- const { keyframes, relative, path } = prepare(animation, options)
445
+ const { keyframes, relative, path, layer } = prepare(animation, options)
365
446
  if (!keyframes) throw new Error(`Unknown preset "${animation}".`)
447
+ if (layer !== undefined)
448
+ return new Map(expand(keyframes, path).map(([property]) => [property, null]))
366
449
  return new Map(
367
450
  expand(keyframes, path).map(([property, [first]]) => [
368
451
  property,
package/src/drag.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  type TouchGestureEventData,
6
6
  type View,
7
7
  } from '@nativescript/core'
8
- import { stop, toView } from './animate.js'
8
+ import { toView } from './animate.js'
9
9
  import { DEFAULT_FRICTION } from './decay.js'
10
10
  import { DragInput } from './drag-input.js'
11
11
  import {
@@ -15,9 +15,18 @@ import {
15
15
  solveDragRelease,
16
16
  type DragCurve,
17
17
  } from './drag-physics.js'
18
- import { currentValue, ViewAnimation, type Plan } from './engine.js'
18
+ import {
19
+ baseValue,
20
+ currentValue,
21
+ setBase,
22
+ stopBase,
23
+ ViewAnimation,
24
+ type Animation,
25
+ type Plan,
26
+ } from './engine.js'
27
+ import { asRoot, isMotionValue, type MotionValue } from './motion-value.js'
19
28
  import type { PlaybackAnimation } from './playback.js'
20
- import { readValue, writeValue } from './properties.js'
29
+ import { createTrack } from './track.js'
21
30
  import type { Target } from './types.js'
22
31
 
23
32
  export interface DragPoint {
@@ -29,10 +38,10 @@ export interface DragEvent extends DragPoint {
29
38
  readonly velocityX: number
30
39
  readonly velocityY: number
31
40
  }
32
- export interface DragRelease extends DragEvent {
41
+ export interface DragRelease<A extends Animation = PlaybackAnimation> extends DragEvent {
33
42
  readonly target: DragPoint
34
43
  /** False on interruption/cancellation. Check before applying completion effects. */
35
- readonly animation: PlaybackAnimation
44
+ readonly animation: A
36
45
  }
37
46
  export interface DragOptions {
38
47
  /** Defaults to both. One finger only. */
@@ -51,6 +60,17 @@ export interface DragOptions {
51
60
  onCancel?: (event: DragEvent) => void
52
61
  onSettle?: (position: DragPoint) => void
53
62
  }
63
+ /** Drags a motion value instead of the view. Bounds and snap points are in the value's units. */
64
+ export interface DragValueOptions extends Omit<
65
+ DragOptions,
66
+ 'axis' | 'snap' | 'onRelease'
67
+ > {
68
+ /** Takes the position on every move and plays the release; bind views to it to follow. */
69
+ value: MotionValue
70
+ axis: 'x' | 'y'
71
+ snap?: readonly number[]
72
+ onRelease?: (event: DragRelease<Animation>) => void
73
+ }
54
74
  export interface Drag {
55
75
  readonly dragging: boolean
56
76
  readonly settling: boolean
@@ -67,22 +87,28 @@ export interface Drag {
67
87
  }
68
88
 
69
89
  type Axis = 'x' | 'y'
90
+ type Root = ReturnType<typeof asRoot>
70
91
  interface Settings {
71
92
  axes: Axis[]
72
93
  curves: Record<Axis, DragCurve>
73
- callbacks: Pick<
74
- DragOptions,
75
- 'onStart' | 'onMove' | 'onRelease' | 'onCancel' | 'onSettle'
76
- >
94
+ value: Root | undefined
95
+ callbacks: Pick<DragOptions, 'onStart' | 'onMove' | 'onCancel' | 'onSettle'> & {
96
+ onRelease?: (event: DragRelease<Animation>) => void
97
+ }
77
98
  }
78
99
  const owners = new WeakMap<View, Drag>()
79
100
 
80
- function settings(options: DragOptions): Settings {
101
+ function settings(options: DragOptions | DragValueOptions): Settings {
81
102
  if (!options || typeof options !== 'object')
82
103
  throw new TypeError('Drag options must be an object.')
83
104
  const axis = options.axis ?? 'both'
84
105
  if (!['x', 'y', 'both'].includes(axis))
85
106
  throw new TypeError('Drag axis must be x, y or both.')
107
+ const value = 'value' in options ? options.value : undefined
108
+ if (value !== undefined && !isMotionValue(value))
109
+ throw new TypeError('Drag value must be a motion value.')
110
+ if (value !== undefined && axis === 'both')
111
+ throw new TypeError('Dragging a motion value needs axis "x" or "y".')
86
112
  const friction = options.friction ?? DEFAULT_FRICTION
87
113
  if (!Number.isFinite(friction) || friction <= 0 || friction >= 1)
88
114
  throw new RangeError('Drag friction must be between 0 and 1 exclusively.')
@@ -152,18 +178,23 @@ function settings(options: DragOptions): Settings {
152
178
  const callbacks = {
153
179
  onStart: options.onStart,
154
180
  onMove: options.onMove,
155
- onRelease: options.onRelease,
181
+ onRelease: options.onRelease as Settings['callbacks']['onRelease'],
156
182
  onCancel: options.onCancel,
157
183
  onSettle: options.onSettle,
158
184
  }
159
185
  for (const callback of Object.values(callbacks))
160
186
  if (callback !== undefined && typeof callback !== 'function')
161
187
  throw new TypeError('Drag callbacks must be functions.')
162
- return { axes, curves, callbacks }
188
+ return { axes, curves, value: value && asRoot(value), callbacks }
163
189
  }
164
190
 
165
- /** Main-thread pan updates; release physics play through Jolt's native animation drivers. */
166
- export function drag(target: Target, options: DragOptions = {}): Drag {
191
+ /**
192
+ * Main-thread pan updates; release physics play through Jolt's native animation drivers.
193
+ * With `value`, the gesture drives a motion value and the views bound to it.
194
+ */
195
+ export function drag(target: Target, options: DragValueOptions): Drag
196
+ export function drag(target: Target, options?: DragOptions): Drag
197
+ export function drag(target: Target, options: DragOptions | DragValueOptions = {}): Drag {
167
198
  const view = toView(target)
168
199
  const config = settings(options) // Invalid replacements leave the existing owner intact.
169
200
  const previous = owners.get(view)
@@ -187,7 +218,7 @@ class DragController implements Drag {
187
218
  private attached = false
188
219
  private blocked = false
189
220
  private generation = 0
190
- private release: PlaybackAnimation | undefined
221
+ private release: Animation | undefined
191
222
  private input = new DragInput()
192
223
  private origin = { x: 0, y: 0 }
193
224
  private last: DragEvent = { x: 0, y: 0, velocityX: 0, velocityY: 0 }
@@ -215,6 +246,8 @@ class DragController implements Drag {
215
246
  return this.destroyed
216
247
  }
217
248
  get position(): DragPoint {
249
+ const value = this.config.value
250
+ if (value) return { x: 0, y: 0, [this.config.axes[0]!]: value.raw() }
218
251
  return { x: currentValue(this.view, 'x'), y: currentValue(this.view, 'y') }
219
252
  }
220
253
  cancel = () => {
@@ -303,9 +336,15 @@ class DragController implements Drag {
303
336
  this.cancel()
304
337
  if (!this.attached || this.destroyed || this.disabled) return
305
338
  this.blocked = false
306
- // Claim just the enabled axes, preserving the native presentation on takeover.
307
- stop(this.view, this.config.axes)
308
- this.origin = { x: readValue(this.view, 'x'), y: readValue(this.view, 'y') }
339
+ const value = this.config.value
340
+ if (value) {
341
+ value.stop()
342
+ this.origin = this.position
343
+ } else {
344
+ // Claim just the enabled axes, preserving the native presentation on takeover.
345
+ stopBase(this.view, this.config.axes)
346
+ this.origin = { x: baseValue(this.view, 'x'), y: baseValue(this.view, 'y') }
347
+ }
309
348
  const size =
310
349
  (this.view.parent as View | undefined)?.getActualSize() ??
311
350
  this.view.getActualSize()
@@ -346,8 +385,11 @@ class DragController implements Drag {
346
385
  : clampDrag(raw, curve.bounds)
347
386
  if (axis === 'x') vx = input.velocityX * resistance
348
387
  else vy = input.velocityY * resistance
349
- stop(this.view, [axis])
350
- writeValue(this.view, axis, position[axis])
388
+ if (this.config.value) this.config.value.set(position[axis])
389
+ else {
390
+ stopBase(this.view, [axis])
391
+ setBase(this.view, axis, position[axis])
392
+ }
351
393
  }
352
394
  this.last = { ...position, velocityX: vx, velocityY: vy }
353
395
  if (event.state !== 3)
@@ -383,7 +425,11 @@ class DragController implements Drag {
383
425
  transition: { curve, delay: 0, repeat: 0, reverse: false },
384
426
  }
385
427
  })
386
- const animation = new ViewAnimation(this.view, plans)
428
+ const [axis] = this.config.axes
429
+ const value = this.config.value
430
+ const animation = value
431
+ ? value.play(createTrack(axis!, [event[axis!]], plans[0]!.transition, 0))
432
+ : new ViewAnimation(this.view, plans)
387
433
  this.release = animation
388
434
  void animation.finished.then((finished) => {
389
435
  if (generation !== this.generation || this.destroyed) return
package/src/easing.ts CHANGED
@@ -15,6 +15,7 @@ export type EasingName =
15
15
  | 'backOut'
16
16
  | 'backInOut'
17
17
  | 'anticipate'
18
+ | `${'quad' | 'cubic' | 'quart' | 'quint' | 'sine' | 'expo'}${'In' | 'Out' | 'InOut'}`
18
19
 
19
20
  export type Easing = EasingName | CubicBezier | EasingFunction
20
21
 
@@ -73,6 +74,13 @@ const mirror =
73
74
  (p) =>
74
75
  p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2
75
76
 
77
+ const family = <N extends string>(name: N, easeIn: EasingFunction) =>
78
+ ({
79
+ [`${name}In`]: easeIn,
80
+ [`${name}Out`]: reverse(easeIn),
81
+ [`${name}InOut`]: mirror(easeIn),
82
+ }) as Record<`${N}${'In' | 'Out' | 'InOut'}`, EasingFunction>
83
+
76
84
  const easings: Record<EasingName, EasingFunction> = {
77
85
  linear,
78
86
  ease: cubicBezier(0.25, 0.1, 0.25, 1),
@@ -87,6 +95,12 @@ const easings: Record<EasingName, EasingFunction> = {
87
95
  backInOut: mirror(backIn),
88
96
  anticipate: (p) =>
89
97
  p <= 0.5 ? backIn(2 * p) / 2 : p >= 1 ? 1 : 1 - Math.pow(2, 10 - 20 * p) / 2,
98
+ ...family('quad', (p) => p ** 2),
99
+ ...family('cubic', (p) => p ** 3),
100
+ ...family('quart', (p) => p ** 4),
101
+ ...family('quint', (p) => p ** 5),
102
+ ...family('sine', (p) => (p >= 1 ? 1 : 1 - Math.cos((p * Math.PI) / 2))),
103
+ ...family('expo', (p) => (p <= 0 ? 0 : 2 ** (10 * p - 10))),
90
104
  }
91
105
 
92
106
  export function resolveEasing(easing: Easing): EasingFunction {