@rafaelaugusto/jolt 0.0.0-stage → 0.1.0-beta.0

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -2
  3. package/dist/animate.d.ts +26 -0
  4. package/dist/animate.d.ts.map +1 -0
  5. package/dist/animate.js +153 -0
  6. package/dist/animate.js.map +1 -0
  7. package/dist/driver/android.d.ts +3 -0
  8. package/dist/driver/android.d.ts.map +1 -0
  9. package/dist/driver/android.js +73 -0
  10. package/dist/driver/android.js.map +1 -0
  11. package/dist/driver/index.d.ts +18 -0
  12. package/dist/driver/index.d.ts.map +1 -0
  13. package/dist/driver/index.js +15 -0
  14. package/dist/driver/index.js.map +1 -0
  15. package/dist/driver/ios.d.ts +3 -0
  16. package/dist/driver/ios.d.ts.map +1 -0
  17. package/dist/driver/ios.js +68 -0
  18. package/dist/driver/ios.js.map +1 -0
  19. package/dist/easing.d.ts +8 -0
  20. package/dist/easing.d.ts.map +1 -0
  21. package/dist/easing.js +71 -0
  22. package/dist/easing.js.map +1 -0
  23. package/dist/engine.d.ts +47 -0
  24. package/dist/engine.d.ts.map +1 -0
  25. package/dist/engine.js +168 -0
  26. package/dist/engine.js.map +1 -0
  27. package/dist/index.d.ts +13 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +7 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/presets.d.ts +295 -0
  32. package/dist/presets.d.ts.map +1 -0
  33. package/dist/presets.js +123 -0
  34. package/dist/presets.js.map +1 -0
  35. package/dist/press.d.ts +13 -0
  36. package/dist/press.d.ts.map +1 -0
  37. package/dist/press.js +32 -0
  38. package/dist/press.js.map +1 -0
  39. package/dist/properties.d.ts +8 -0
  40. package/dist/properties.d.ts.map +1 -0
  41. package/dist/properties.js +42 -0
  42. package/dist/properties.js.map +1 -0
  43. package/dist/sequence.d.ts +25 -0
  44. package/dist/sequence.d.ts.map +1 -0
  45. package/dist/sequence.js +68 -0
  46. package/dist/sequence.js.map +1 -0
  47. package/dist/spring.d.ts +22 -0
  48. package/dist/spring.d.ts.map +1 -0
  49. package/dist/spring.js +54 -0
  50. package/dist/spring.js.map +1 -0
  51. package/dist/track.d.ts +28 -0
  52. package/dist/track.d.ts.map +1 -0
  53. package/dist/track.js +103 -0
  54. package/dist/track.js.map +1 -0
  55. package/dist/transition.d.ts +58 -0
  56. package/dist/transition.d.ts.map +1 -0
  57. package/dist/transition.js +82 -0
  58. package/dist/transition.js.map +1 -0
  59. package/dist/types.d.ts +38 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +2 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +63 -4
  64. package/src/animate.ts +219 -0
  65. package/src/driver/android.ts +101 -0
  66. package/src/driver/index.ts +32 -0
  67. package/src/driver/ios.ts +88 -0
  68. package/src/easing.ts +100 -0
  69. package/src/engine.ts +208 -0
  70. package/src/index.ts +12 -0
  71. package/src/presets.ts +134 -0
  72. package/src/press.ts +43 -0
  73. package/src/properties.ts +51 -0
  74. package/src/sequence.ts +85 -0
  75. package/src/spring.ts +95 -0
  76. package/src/track.ts +163 -0
  77. package/src/transition.ts +160 -0
  78. package/src/types.ts +39 -0
package/src/animate.ts ADDED
@@ -0,0 +1,219 @@
1
+ import { View } from '@nativescript/core'
2
+ import {
3
+ arrivalOf,
4
+ setArrival,
5
+ stopView,
6
+ ViewAnimation,
7
+ type Animation,
8
+ type Plan,
9
+ } from './engine.js'
10
+ import { presets, type PresetName } from './presets.js'
11
+ import { isProperty, type Property } from './properties.js'
12
+ import {
13
+ hasCurve,
14
+ mergeTransitions,
15
+ resolveTransition,
16
+ springs,
17
+ type SpringName,
18
+ type Transition,
19
+ } from './transition.js'
20
+ import type {
21
+ AnimateOptions,
22
+ Keyframes,
23
+ KeyframeValue,
24
+ Stagger,
25
+ Target,
26
+ } from './types.js'
27
+
28
+ interface Options {
29
+ base: Transition
30
+ overrides: Partial<Record<keyof Keyframes, Transition>>
31
+ stagger: number | Stagger | undefined
32
+ }
33
+
34
+ /**
35
+ * Animates one view, or several. Takes keyframes or the name of a preset,
36
+ * and a transition or the name of a spring.
37
+ *
38
+ * ```ts
39
+ * animate(card, 'fadeInUp')
40
+ * animate(card, { scale: 1.1, rotate: [0, 90] }, 'bouncy')
41
+ * animate(rows, 'fadeInUp', { stagger: 40 })
42
+ * ```
43
+ */
44
+ export function animate(
45
+ target: Target | readonly Target[],
46
+ animation: Keyframes | PresetName,
47
+ options?: AnimateOptions | SpringName,
48
+ ): Animation {
49
+ const { keyframes, base, overrides, stagger } = prepare(animation, options)
50
+
51
+ if (!isList(target)) {
52
+ return new ViewAnimation(toView(target), plan(keyframes, base, overrides, 0))
53
+ }
54
+
55
+ const views = target.map(toView)
56
+ return group(
57
+ views.map(
58
+ (view, index) =>
59
+ new ViewAnimation(
60
+ view,
61
+ plan(keyframes, base, overrides, staggerDelay(stagger, index, views.length)),
62
+ ),
63
+ ),
64
+ )
65
+ }
66
+
67
+ /** Stops every animation running on the view, leaving it where it is. */
68
+ export function stop(target: Target | readonly Target[]) {
69
+ for (const view of toViews(target)) stopView(view)
70
+ }
71
+
72
+ /** Plays several animations as one: finished once they all are, and stopped together. */
73
+ export function group(animations: readonly Animation[]): Animation {
74
+ const finished = Promise.all(animations.map((animation) => animation.finished)).then(
75
+ (results) => results.every(Boolean),
76
+ )
77
+ const animation: Animation = {
78
+ finished,
79
+ stop: () => animations.forEach((animation) => animation.stop()),
80
+ then: (onfulfilled, onrejected) => finished.then(onfulfilled, onrejected),
81
+ }
82
+ setArrival(animation, Promise.all(animations.map(arrivalOf)))
83
+ return animation
84
+ }
85
+
86
+ export function toView(target: Target): View {
87
+ if (target instanceof View) return target
88
+ const view =
89
+ (target as { nativeView?: unknown } | null)?.nativeView ??
90
+ (target as { nativeElement?: unknown } | null)?.nativeElement
91
+ if (view instanceof View) return view
92
+ throw new TypeError(`Expected a NativeScript view, got ${String(target)}.`)
93
+ }
94
+
95
+ export function toViews(target: Target | readonly Target[]): View[] {
96
+ return isList(target) ? target.map(toView) : [toView(target)]
97
+ }
98
+
99
+ function isList<T>(target: T | readonly T[]): target is readonly T[] {
100
+ return Array.isArray(target)
101
+ }
102
+
103
+ function prepare(
104
+ animation: Keyframes | PresetName,
105
+ options?: AnimateOptions | SpringName,
106
+ ) {
107
+ const user = normalize(options)
108
+ if (typeof animation !== 'string') return { keyframes: animation, ...user }
109
+
110
+ const preset = presets[animation]
111
+ if (!preset) throw new Error(`Unknown preset "${animation}".`)
112
+ const own = normalize(preset.transition)
113
+ return {
114
+ keyframes: preset.keyframes as Keyframes,
115
+ base: mergeTransitions(own.base, user.base),
116
+ overrides: hasCurve(user.base)
117
+ ? user.overrides
118
+ : { ...own.overrides, ...user.overrides },
119
+ stagger: user.stagger,
120
+ }
121
+ }
122
+
123
+ function normalize(options: AnimateOptions | SpringName | undefined): Options {
124
+ if (options === undefined) return { base: {}, overrides: {}, stagger: undefined }
125
+ if (typeof options === 'string')
126
+ return { base: spring(options), overrides: {}, stagger: undefined }
127
+
128
+ const base: Record<string, unknown> = {}
129
+ const overrides: Options['overrides'] = {}
130
+ let stagger: Options['stagger']
131
+ for (const [key, value] of Object.entries(options)) {
132
+ if (value === undefined) continue
133
+ if (key === 'stagger') stagger = value as Options['stagger']
134
+ else if (key === 'scale' || isProperty(key)) {
135
+ overrides[key] = typeof value === 'string' ? spring(value as SpringName) : value
136
+ } else base[key] = value
137
+ }
138
+ return { base, overrides, stagger }
139
+ }
140
+
141
+ function plan(
142
+ keyframes: Keyframes,
143
+ base: Transition,
144
+ overrides: Options['overrides'],
145
+ offset: number,
146
+ ): Plan[] {
147
+ return expand(keyframes).map(([property, frames]) => {
148
+ const override =
149
+ overrides[property] ?? (property.startsWith('scale') ? overrides.scale : undefined)
150
+ const transition = resolveTransition(
151
+ override ? mergeTransitions(base, override) : base,
152
+ property,
153
+ frames.length,
154
+ )
155
+ transition.delay += offset
156
+ return { property, keyframes: frames, transition }
157
+ })
158
+ }
159
+
160
+ /** Each property the animation touches, with the value it starts from when it sets one. */
161
+ export function startingValues(animation: Keyframes | PresetName) {
162
+ const keyframes =
163
+ typeof animation === 'string' ? presets[animation]?.keyframes : animation
164
+ if (!keyframes) throw new Error(`Unknown preset "${animation}".`)
165
+ return new Map(
166
+ expand(keyframes as Keyframes).map(([property, [first]]) => [property, first!]),
167
+ )
168
+ }
169
+
170
+ function expand(keyframes: Keyframes): [Property, (number | null)[]][] {
171
+ const expanded: [Property, (number | null)[]][] = []
172
+ for (const [key, value] of Object.entries(keyframes) as [
173
+ keyof Keyframes,
174
+ KeyframeValue | undefined,
175
+ ][]) {
176
+ if (value === undefined) continue
177
+ if (key !== 'scale' && !isProperty(key)) throw new Error(`Can't animate "${key}".`)
178
+
179
+ const properties: Property[] = key === 'scale' ? ['scaleX', 'scaleY'] : [key]
180
+ for (const property of properties) {
181
+ if (key === 'scale' && keyframes[property] !== undefined) continue
182
+ expanded.push([property, toKeyframes(property, value)])
183
+ }
184
+ }
185
+ return expanded
186
+ }
187
+
188
+ function toKeyframes(property: Property, value: KeyframeValue): (number | null)[] {
189
+ const frames = typeof value === 'number' ? [null, value] : [...value]
190
+ if (frames.length === 1) frames.unshift(null)
191
+ if (
192
+ frames.length === 0 ||
193
+ frames.some((frame) => frame !== null && !Number.isFinite(frame))
194
+ ) {
195
+ throw new Error(`Invalid keyframes for "${property}": ${JSON.stringify(value)}.`)
196
+ }
197
+ return frames
198
+ }
199
+
200
+ function staggerDelay(stagger: Options['stagger'], index: number, count: number) {
201
+ if (stagger === undefined) return 0
202
+ const { each, from = 'first' } =
203
+ typeof stagger === 'number' ? { each: stagger } : stagger
204
+ const origin =
205
+ from === 'first'
206
+ ? 0
207
+ : from === 'last'
208
+ ? count - 1
209
+ : from === 'center'
210
+ ? (count - 1) / 2
211
+ : from
212
+ return each * Math.abs(index - origin)
213
+ }
214
+
215
+ function spring(name: SpringName): Transition {
216
+ const transition = springs[name]
217
+ if (!transition) throw new Error(`Unknown spring "${name}".`)
218
+ return transition
219
+ }
@@ -0,0 +1,101 @@
1
+ import { Utils, type View } from '@nativescript/core'
2
+ import type { Property } from '../properties.js'
3
+ import { after, type Driver } from './index.js'
4
+
5
+ type NativeView = android.view.View
6
+
7
+ const accessors: Record<
8
+ Property,
9
+ { get(view: NativeView): number; set(view: NativeView, value: number): void }
10
+ > = {
11
+ opacity: { get: (v) => v.getAlpha(), set: (v, value) => v.setAlpha(value) },
12
+ x: { get: (v) => v.getTranslationX(), set: (v, value) => v.setTranslationX(value) },
13
+ y: { get: (v) => v.getTranslationY(), set: (v, value) => v.setTranslationY(value) },
14
+ scaleX: { get: (v) => v.getScaleX(), set: (v, value) => v.setScaleX(value) },
15
+ scaleY: { get: (v) => v.getScaleY(), set: (v, value) => v.setScaleY(value) },
16
+ rotate: { get: (v) => v.getRotation(), set: (v, value) => v.setRotation(value) },
17
+ rotateX: { get: (v) => v.getRotationX(), set: (v, value) => v.setRotationX(value) },
18
+ rotateY: { get: (v) => v.getRotationY(), set: (v, value) => v.setRotationY(value) },
19
+ }
20
+
21
+ const unit = (property: Property) =>
22
+ property === 'x' || property === 'y' ? Utils.layout.getDisplayDensity() : 1
23
+
24
+ export function createAndroidDriver(): Driver {
25
+ const { View } = android.view
26
+ const nativeProperties = {
27
+ opacity: View.ALPHA,
28
+ x: View.TRANSLATION_X,
29
+ y: View.TRANSLATION_Y,
30
+ scaleX: View.SCALE_X,
31
+ scaleY: View.SCALE_Y,
32
+ rotate: View.ROTATION,
33
+ rotateX: View.ROTATION_X,
34
+ rotateY: View.ROTATION_Y,
35
+ } satisfies Record<Property, unknown>
36
+ const interpolator = new android.view.animation.LinearInterpolator()
37
+
38
+ return {
39
+ now: () => android.os.SystemClock.uptimeMillis(),
40
+
41
+ play(view, track, _startedAt, onEnd) {
42
+ const nativeView = view.nativeViewProtected as NativeView
43
+ const scale = unit(track.property)
44
+
45
+ const keyframes = Array.create(android.animation.Keyframe, track.values.length)
46
+ track.values.forEach((value, i) => {
47
+ keyframes[i] = android.animation.Keyframe.ofFloat(track.times[i]!, value * scale)
48
+ })
49
+ const holders = Array.create(android.animation.PropertyValuesHolder, 1)
50
+ holders[0] = android.animation.PropertyValuesHolder.ofKeyframe(
51
+ nativeProperties[track.property],
52
+ keyframes,
53
+ )
54
+
55
+ const animator = android.animation.ObjectAnimator.ofPropertyValuesHolder(
56
+ nativeView,
57
+ holders,
58
+ )
59
+ animator.setDuration(Math.max(track.duration, 1))
60
+ animator.setStartDelay(track.delay)
61
+ animator.setInterpolator(interpolator)
62
+ if (track.repeat > 0) {
63
+ animator.setRepeatCount(
64
+ Number.isFinite(track.repeat)
65
+ ? track.repeat
66
+ : android.animation.ValueAnimator.INFINITE,
67
+ )
68
+ animator.setRepeatMode(
69
+ track.reverse
70
+ ? android.animation.ValueAnimator.REVERSE
71
+ : android.animation.ValueAnimator.RESTART,
72
+ )
73
+ }
74
+ animator.addListener(
75
+ new android.animation.Animator.AnimatorListener({
76
+ onAnimationStart() {},
77
+ onAnimationRepeat() {},
78
+ onAnimationCancel() {},
79
+ onAnimationEnd() {
80
+ onEnd()
81
+ },
82
+ }),
83
+ )
84
+
85
+ accessors[track.property].set(nativeView, track.values[0]! * scale)
86
+ animator.start()
87
+ return animator
88
+ },
89
+
90
+ stop(_view, handle) {
91
+ ;(handle as android.animation.ObjectAnimator).cancel()
92
+ },
93
+
94
+ presented(view, property) {
95
+ const nativeView = view.nativeViewProtected as NativeView | undefined
96
+ return nativeView ? accessors[property].get(nativeView) / unit(property) : undefined
97
+ },
98
+
99
+ after,
100
+ }
101
+ }
@@ -0,0 +1,32 @@
1
+ import { isAndroid, type View } from '@nativescript/core'
2
+ import type { Property } from '../properties.js'
3
+ import type { Track } from '../track.js'
4
+ import { createAndroidDriver } from './android.js'
5
+ import { createIOSDriver } from './ios.js'
6
+
7
+ export interface Driver {
8
+ /** Milliseconds, on the clock the platform's animations run on. */
9
+ now(): number
10
+ /** Starts the track at `startedAt + track.delay` and calls `onEnd` once, however it ends. */
11
+ play(view: View, track: Track, startedAt: number, onEnd: () => void): unknown
12
+ stop(view: View, handle: unknown): void
13
+ /** The value on screen, when the platform knows it better than the track does. */
14
+ presented(view: View, property: Property): number | undefined
15
+ /** Calls back after `ms` on the same clock, and returns a function that cancels it. */
16
+ after(ms: number, callback: () => void): () => void
17
+ }
18
+
19
+ export function after(ms: number, callback: () => void) {
20
+ const timer = setTimeout(callback, ms)
21
+ return () => clearTimeout(timer)
22
+ }
23
+
24
+ let driver: Driver | undefined
25
+
26
+ export function getDriver(): Driver {
27
+ return (driver ??= isAndroid ? createAndroidDriver() : createIOSDriver())
28
+ }
29
+
30
+ export function setDriver(next: Driver | undefined) {
31
+ driver = next
32
+ }
@@ -0,0 +1,88 @@
1
+ import type { View } from '@nativescript/core'
2
+ import type { Property } from '../properties.js'
3
+ import { after, type Driver } from './index.js'
4
+
5
+ interface Handle {
6
+ layers: CALayer[]
7
+ key: string
8
+ }
9
+
10
+ const keyPaths: Record<Property, string> = {
11
+ opacity: 'opacity',
12
+ x: 'transform.translation.x',
13
+ y: 'transform.translation.y',
14
+ scaleX: 'transform.scale.x',
15
+ scaleY: 'transform.scale.y',
16
+ rotate: 'transform.rotation.z',
17
+ rotateX: 'transform.rotation.x',
18
+ rotateY: 'transform.rotation.y',
19
+ }
20
+
21
+ const radians = (degrees: number) => (degrees * Math.PI) / 180
22
+
23
+ function layersOf(view: View): CALayer[] {
24
+ const nativeView = view.nativeViewProtected as UIView & {
25
+ outerShadowContainerLayer?: CALayer
26
+ }
27
+ return nativeView.outerShadowContainerLayer
28
+ ? [nativeView.layer, nativeView.outerShadowContainerLayer]
29
+ : [nativeView.layer]
30
+ }
31
+
32
+ /** A 3D rotation only looks 3D with perspective, which core leaves out once the rotation ends at 0. */
33
+ function ensurePerspective(view: View, layers: CALayer[]) {
34
+ CATransaction.begin()
35
+ CATransaction.setDisableActions(true)
36
+ for (const layer of layers) {
37
+ const transform = layer.transform
38
+ if (transform.m34 === 0) {
39
+ transform.m34 = -1 / (view.perspective || 300)
40
+ layer.transform = transform
41
+ }
42
+ }
43
+ CATransaction.commit()
44
+ }
45
+
46
+ export function createIOSDriver(): Driver {
47
+ return {
48
+ now: () => CACurrentMediaTime() * 1000,
49
+
50
+ play(view, track, startedAt, onEnd): Handle {
51
+ const layers = layersOf(view)
52
+ const key = `jolt:${track.property}`
53
+ const rotation = track.property.startsWith('rotate')
54
+ if (track.property === 'rotateX' || track.property === 'rotateY') {
55
+ ensurePerspective(view, layers)
56
+ }
57
+
58
+ const animation = CAKeyframeAnimation.animationWithKeyPath(keyPaths[track.property])
59
+ animation.values = NSArray.arrayWithArray(
60
+ track.values.map((value) => (rotation ? radians(value) : value)),
61
+ )
62
+ animation.keyTimes = NSArray.arrayWithArray(track.times as number[])
63
+ animation.duration = Math.max(track.duration, 1) / 1000
64
+ animation.beginTime = (startedAt + track.delay) / 1000
65
+ animation.fillMode = kCAFillModeBackwards
66
+ if (track.repeat > 0) {
67
+ animation.autoreverses = track.reverse
68
+ animation.repeatCount = track.reverse ? (track.repeat + 1) / 2 : track.repeat + 1
69
+ }
70
+
71
+ CATransaction.begin()
72
+ CATransaction.setCompletionBlock(onEnd)
73
+ for (const layer of layers) layer.addAnimationForKey(animation, key)
74
+ CATransaction.commit()
75
+
76
+ return { layers, key }
77
+ },
78
+
79
+ stop(_view, handle) {
80
+ const { layers, key } = handle as Handle
81
+ for (const layer of layers) layer.removeAnimationForKey(key)
82
+ },
83
+
84
+ presented: () => undefined,
85
+
86
+ after,
87
+ }
88
+ }
package/src/easing.ts ADDED
@@ -0,0 +1,100 @@
1
+ export type EasingFunction = (progress: number) => number
2
+
3
+ export type CubicBezier = readonly [x1: number, y1: number, x2: number, y2: number]
4
+
5
+ export type EasingName =
6
+ | 'linear'
7
+ | 'ease'
8
+ | 'easeIn'
9
+ | 'easeOut'
10
+ | 'easeInOut'
11
+ | 'circIn'
12
+ | 'circOut'
13
+ | 'circInOut'
14
+ | 'backIn'
15
+ | 'backOut'
16
+ | 'backInOut'
17
+ | 'anticipate'
18
+
19
+ export type Easing = EasingName | CubicBezier | EasingFunction
20
+
21
+ export const linear: EasingFunction = (p) => p
22
+
23
+ export function cubicBezier(x1: number, y1: number, x2: number, y2: number) {
24
+ if (x1 === y1 && x2 === y2) return linear
25
+
26
+ const cx = 3 * x1
27
+ const bx = 3 * (x2 - x1) - cx
28
+ const ax = 1 - cx - bx
29
+ const cy = 3 * y1
30
+ const by = 3 * (y2 - y1) - cy
31
+ const ay = 1 - cy - by
32
+
33
+ const sampleX = (t: number) => ((ax * t + bx) * t + cx) * t
34
+ const sampleY = (t: number) => ((ay * t + by) * t + cy) * t
35
+ const slopeX = (t: number) => (3 * ax * t + 2 * bx) * t + cx
36
+
37
+ const solveX = (x: number) => {
38
+ let t = x
39
+ for (let i = 0; i < 8; i++) {
40
+ const error = sampleX(t) - x
41
+ if (Math.abs(error) < 1e-7) return t
42
+ const slope = slopeX(t)
43
+ if (Math.abs(slope) < 1e-6) break
44
+ t -= error / slope
45
+ }
46
+
47
+ let low = 0
48
+ let high = 1
49
+ t = x
50
+ for (let i = 0; i < 40; i++) {
51
+ const value = sampleX(t)
52
+ if (Math.abs(value - x) < 1e-7) break
53
+ if (value < x) low = t
54
+ else high = t
55
+ t = (low + high) / 2
56
+ }
57
+ return t
58
+ }
59
+
60
+ return (p: number) => (p <= 0 ? 0 : p >= 1 ? 1 : sampleY(solveX(p)))
61
+ }
62
+
63
+ const backIn = cubicBezier(0.36, 0, 0.66, -0.56)
64
+ const circIn: EasingFunction = (p) => 1 - Math.sqrt(1 - Math.min(p * p, 1))
65
+
66
+ const reverse =
67
+ (easing: EasingFunction): EasingFunction =>
68
+ (p) =>
69
+ 1 - easing(1 - p)
70
+
71
+ const mirror =
72
+ (easing: EasingFunction): EasingFunction =>
73
+ (p) =>
74
+ p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2
75
+
76
+ const easings: Record<EasingName, EasingFunction> = {
77
+ linear,
78
+ ease: cubicBezier(0.25, 0.1, 0.25, 1),
79
+ easeIn: cubicBezier(0.42, 0, 1, 1),
80
+ easeOut: cubicBezier(0, 0, 0.58, 1),
81
+ easeInOut: cubicBezier(0.42, 0, 0.58, 1),
82
+ circIn,
83
+ circOut: reverse(circIn),
84
+ circInOut: mirror(circIn),
85
+ backIn,
86
+ backOut: reverse(backIn),
87
+ backInOut: mirror(backIn),
88
+ anticipate: (p) =>
89
+ p <= 0.5 ? backIn(2 * p) / 2 : p >= 1 ? 1 : 1 - Math.pow(2, 10 - 20 * p) / 2,
90
+ }
91
+
92
+ export function resolveEasing(easing: Easing): EasingFunction {
93
+ if (typeof easing === 'function') return easing
94
+ if (typeof easing === 'string') {
95
+ const resolved = easings[easing]
96
+ if (!resolved) throw new Error(`Unknown easing "${easing}".`)
97
+ return resolved
98
+ }
99
+ return cubicBezier(...easing)
100
+ }