@rafaelaugusto/jolt 0.1.0-beta.10 → 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 (92) hide show
  1. package/README.md +55 -6
  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/driver/android-gradient.d.ts +5 -0
  11. package/dist/driver/android-gradient.d.ts.map +1 -0
  12. package/dist/driver/android-gradient.js +89 -0
  13. package/dist/driver/android-gradient.js.map +1 -0
  14. package/dist/driver/ios-gradient.d.ts +5 -0
  15. package/dist/driver/ios-gradient.d.ts.map +1 -0
  16. package/dist/driver/ios-gradient.js +109 -0
  17. package/dist/driver/ios-gradient.js.map +1 -0
  18. package/dist/easing.d.ts +1 -1
  19. package/dist/easing.d.ts.map +1 -1
  20. package/dist/easing.js +11 -0
  21. package/dist/easing.js.map +1 -1
  22. package/dist/engine.d.ts +18 -1
  23. package/dist/engine.d.ts.map +1 -1
  24. package/dist/engine.js +143 -36
  25. package/dist/engine.js.map +1 -1
  26. package/dist/gradient.d.ts +90 -0
  27. package/dist/gradient.d.ts.map +1 -0
  28. package/dist/gradient.js +253 -0
  29. package/dist/gradient.js.map +1 -0
  30. package/dist/index.d.ts +7 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/keyboard.d.ts.map +1 -1
  35. package/dist/keyboard.js +63 -10
  36. package/dist/keyboard.js.map +1 -1
  37. package/dist/mixer.d.ts +49 -0
  38. package/dist/mixer.d.ts.map +1 -0
  39. package/dist/mixer.js +238 -0
  40. package/dist/mixer.js.map +1 -0
  41. package/dist/motion-scope.d.ts +4 -1
  42. package/dist/motion-scope.d.ts.map +1 -1
  43. package/dist/motion-scope.js +8 -3
  44. package/dist/motion-scope.js.map +1 -1
  45. package/dist/motion-states.d.ts +18 -6
  46. package/dist/motion-states.d.ts.map +1 -1
  47. package/dist/motion-states.js +114 -26
  48. package/dist/motion-states.js.map +1 -1
  49. package/dist/motion-value.d.ts +115 -0
  50. package/dist/motion-value.d.ts.map +1 -0
  51. package/dist/motion-value.js +437 -0
  52. package/dist/motion-value.js.map +1 -0
  53. package/dist/path.d.ts.map +1 -1
  54. package/dist/path.js +2 -2
  55. package/dist/path.js.map +1 -1
  56. package/dist/track.d.ts.map +1 -1
  57. package/dist/track.js +31 -12
  58. package/dist/track.js.map +1 -1
  59. package/dist/transition.d.ts +30 -4
  60. package/dist/transition.d.ts.map +1 -1
  61. package/dist/transition.js +63 -7
  62. package/dist/transition.js.map +1 -1
  63. package/dist/types.d.ts +5 -0
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/vue/index.d.ts +28 -2
  66. package/dist/vue/index.d.ts.map +1 -1
  67. package/dist/vue/index.js +78 -2
  68. package/dist/vue/index.js.map +1 -1
  69. package/package.json +1 -1
  70. package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
  71. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrame.java +53 -0
  72. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrameFollower.java +57 -0
  73. package/platforms/android/proguard-rules.pro +2 -0
  74. package/src/android-plugin.d.ts +56 -0
  75. package/src/animate.ts +99 -16
  76. package/src/drag.ts +67 -21
  77. package/src/driver/android-gradient.ts +109 -0
  78. package/src/driver/ios-gradient.ts +114 -0
  79. package/src/easing.ts +14 -0
  80. package/src/engine.ts +158 -53
  81. package/src/gradient.ts +378 -0
  82. package/src/index.ts +34 -3
  83. package/src/keyboard.ts +83 -18
  84. package/src/mixer.ts +264 -0
  85. package/src/motion-scope.ts +14 -6
  86. package/src/motion-states.ts +145 -41
  87. package/src/motion-value.ts +563 -0
  88. package/src/path.ts +2 -1
  89. package/src/track.ts +37 -12
  90. package/src/transition.ts +99 -10
  91. package/src/types.ts +5 -0
  92. package/src/vue/index.ts +98 -1
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
@@ -0,0 +1,109 @@
1
+ import { Utils, type View } from '@nativescript/core'
2
+ import type { GradientFrame, GradientPainter, GradientType } from '../gradient.js'
3
+
4
+ const floats = (values: readonly number[]) => {
5
+ const array = Array.create('float', values.length)
6
+ values.forEach((value, i) => (array[i] = value))
7
+ return array
8
+ }
9
+ const ints = (values: readonly number[]) => {
10
+ const array = Array.create('int', values.length)
11
+ values.forEach((value, i) => (array[i] = value | 0))
12
+ return array
13
+ }
14
+
15
+ /** A gradient drawable stacked on the view's own background: above its color, below its children. */
16
+ export function androidGradient(
17
+ view: View,
18
+ type: GradientType,
19
+ ): GradientPainter | undefined {
20
+ let layer: org.rafaelaugusto.jolt.GradientLayer
21
+ try {
22
+ layer = new org.rafaelaugusto.jolt.GradientLayer(
23
+ type === 'linear' ? 0 : type === 'radial' ? 1 : 2,
24
+ )
25
+ } catch {
26
+ // A JS-only update cannot install the native plugin.
27
+ return undefined
28
+ }
29
+ let stack: android.graphics.drawable.LayerDrawable | undefined
30
+ let queued = false
31
+
32
+ const wrap = () => {
33
+ const native = view.nativeViewProtected as android.view.View | undefined
34
+ if (!native) return
35
+ const corner = typeof view.borderRadius === 'number' ? view.borderRadius : 0
36
+ layer.setCorner(corner * Utils.layout.getDisplayDensity())
37
+ const background = native.getBackground()
38
+ if (stack && background === stack) return
39
+ const layers = Array.create(android.graphics.drawable.Drawable, 2)
40
+ layers[0] = background ?? new android.graphics.drawable.ColorDrawable(0)
41
+ layers[1] = layer
42
+ stack = new android.graphics.drawable.LayerDrawable(layers)
43
+ native.setBackground(stack)
44
+ }
45
+ // Core replaces the background drawable when its style changes; stack on the new one.
46
+ const rewrap = () => {
47
+ if (queued) return
48
+ queued = true
49
+ void Promise.resolve().then(() => {
50
+ queued = false
51
+ wrap()
52
+ })
53
+ }
54
+ view.on('loaded', wrap)
55
+ view.style.on('backgroundInternalChange', rewrap)
56
+ view.style.on('borderTopLeftRadiusChange', rewrap)
57
+ wrap()
58
+
59
+ return {
60
+ paint(frame: GradientFrame) {
61
+ layer.paint(
62
+ ints(frame.colors),
63
+ floats(frame.stops),
64
+ frame.angle,
65
+ frame.x,
66
+ frame.y,
67
+ frame.radius,
68
+ )
69
+ },
70
+ play(frames, times, timing, onEnd) {
71
+ let active = true
72
+ layer.play(
73
+ floats(times),
74
+ ints(frames.flatMap((frame) => frame.colors)),
75
+ floats(frames.flatMap((frame) => frame.stops)),
76
+ floats(frames.flatMap((frame) => [frame.angle, frame.x, frame.y, frame.radius])),
77
+ Math.round(timing.duration),
78
+ Math.round(timing.delay),
79
+ timing.repeat === Infinity ? -1 : timing.repeat,
80
+ timing.reverse,
81
+ new org.rafaelaugusto.jolt.GradientLayer.Listener({
82
+ onEnd: (finished: boolean) => {
83
+ if (!active) return
84
+ active = false
85
+ onEnd(finished)
86
+ },
87
+ }),
88
+ )
89
+ return {
90
+ cancel() {
91
+ if (!active) return
92
+ active = false
93
+ layer.cancel()
94
+ onEnd(false)
95
+ },
96
+ }
97
+ },
98
+ remove() {
99
+ view.off('loaded', wrap)
100
+ view.style.off('backgroundInternalChange', rewrap)
101
+ view.style.off('borderTopLeftRadiusChange', rewrap)
102
+ layer.cancel()
103
+ const native = view.nativeViewProtected as android.view.View | undefined
104
+ if (native && stack && native.getBackground() === stack)
105
+ native.setBackground(stack.getDrawable(0))
106
+ stack = undefined
107
+ },
108
+ }
109
+ }
@@ -0,0 +1,114 @@
1
+ import { Color, type View } from '@nativescript/core'
2
+ import {
3
+ gradientPoints,
4
+ type GradientFrame,
5
+ type GradientPainter,
6
+ type GradientType,
7
+ } from '../gradient.js'
8
+
9
+ const keys = ['colors', 'locations', 'startPoint', 'endPoint'] as const
10
+
11
+ /** A CAGradientLayer at the back of the view's layer: above its background, below its children. */
12
+ export function iosGradient(view: View, type: GradientType): GradientPainter {
13
+ const layer = CAGradientLayer.new()
14
+ layer.type =
15
+ type === 'linear'
16
+ ? kCAGradientLayerAxial
17
+ : type === 'radial'
18
+ ? kCAGradientLayerRadial
19
+ : kCAGradientLayerConic
20
+ let current: GradientFrame | undefined
21
+ let generation = 0
22
+
23
+ const native = () => view.nativeViewProtected as UIView | undefined
24
+ const size = () => {
25
+ const bounds = native()?.layer.bounds
26
+ return { width: bounds?.size.width ?? 0, height: bounds?.size.height ?? 0 }
27
+ }
28
+ const values = (frame: GradientFrame) => {
29
+ const { width, height } = size()
30
+ const { start, end: toward } = gradientPoints(type, frame, width, height)
31
+ // Core Animation starts a conic gradient on the side opposite its end point.
32
+ const end =
33
+ type === 'conic' ? { x: 2 * start.x - toward.x, y: 2 * start.y - toward.y } : toward
34
+ return {
35
+ colors: NSArray.arrayWithArray(
36
+ frame.colors.map((argb) => new Color(argb).ios.CGColor),
37
+ ),
38
+ locations: NSArray.arrayWithArray(frame.stops),
39
+ startPoint: NSValue.valueWithCGPoint(CGPointMake(start.x, start.y)),
40
+ endPoint: NSValue.valueWithCGPoint(CGPointMake(end.x, end.y)),
41
+ }
42
+ }
43
+ const attach = () => {
44
+ const host = native()
45
+ if (!host) return
46
+ CATransaction.begin()
47
+ CATransaction.setDisableActions(true)
48
+ if (layer.superlayer !== host.layer) host.layer.insertSublayerAtIndex(layer, 0)
49
+ layer.frame = host.layer.bounds
50
+ layer.cornerRadius = host.layer.cornerRadius
51
+ if (current) {
52
+ const model = values(current)
53
+ layer.colors = model.colors
54
+ layer.locations = model.locations
55
+ layer.startPoint = model.startPoint.CGPointValue
56
+ layer.endPoint = model.endPoint.CGPointValue
57
+ }
58
+ CATransaction.commit()
59
+ }
60
+ view.on('loaded', attach)
61
+ view.on('layoutChanged', attach)
62
+
63
+ return {
64
+ paint(frame) {
65
+ current = frame
66
+ generation++
67
+ for (const key of keys) layer.removeAnimationForKey(`jolt:gradient:${key}`)
68
+ attach()
69
+ },
70
+ play(frames, times, timing, onEnd) {
71
+ const instance = ++generation
72
+ current = frames[frames.length - 1]!
73
+ attach()
74
+ const sampled = frames.map(values)
75
+ const now = CACurrentMediaTime()
76
+ CATransaction.begin()
77
+ if (timing.repeat !== Infinity)
78
+ CATransaction.setCompletionBlock(() => {
79
+ if (instance === generation) onEnd(true)
80
+ })
81
+ for (const key of keys) {
82
+ const animation = CAKeyframeAnimation.animationWithKeyPath(key)
83
+ animation.values = NSArray.arrayWithArray(sampled.map((value) => value[key]))
84
+ animation.keyTimes = NSArray.arrayWithArray(times as number[])
85
+ animation.duration = Math.max(timing.duration, 1) / 1000
86
+ animation.beginTime = layer.convertTimeFromLayer(now + timing.delay / 1000, null)
87
+ animation.fillMode = kCAFillModeBackwards
88
+ if (timing.repeat > 0) {
89
+ animation.autoreverses = timing.reverse
90
+ animation.repeatCount = timing.reverse
91
+ ? (timing.repeat + 1) / 2
92
+ : timing.repeat + 1
93
+ }
94
+ layer.addAnimationForKey(animation, `jolt:gradient:${key}`)
95
+ }
96
+ CATransaction.commit()
97
+ return {
98
+ cancel() {
99
+ if (instance !== generation) return
100
+ generation++
101
+ for (const key of keys) layer.removeAnimationForKey(`jolt:gradient:${key}`)
102
+ onEnd(false)
103
+ },
104
+ }
105
+ },
106
+ remove() {
107
+ generation++
108
+ view.off('loaded', attach)
109
+ view.off('layoutChanged', attach)
110
+ layer.removeAllAnimations()
111
+ layer.removeFromSuperlayer()
112
+ },
113
+ }
114
+ }
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 {