@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.
- package/README.md +38 -7
- package/dist/animate.d.ts +14 -4
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +50 -34
- package/dist/animate.js.map +1 -1
- package/dist/drag.d.ts +17 -3
- package/dist/drag.d.ts.map +1 -1
- package/dist/drag.js +34 -11
- package/dist/drag.js.map +1 -1
- package/dist/easing.d.ts +1 -1
- package/dist/easing.d.ts.map +1 -1
- package/dist/easing.js +11 -0
- package/dist/easing.js.map +1 -1
- package/dist/engine.d.ts +18 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +143 -36
- package/dist/engine.js.map +1 -1
- package/dist/gradient.d.ts.map +1 -1
- package/dist/gradient.js +10 -4
- package/dist/gradient.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js +63 -10
- package/dist/keyboard.js.map +1 -1
- package/dist/mixer.d.ts +49 -0
- package/dist/mixer.d.ts.map +1 -0
- package/dist/mixer.js +238 -0
- package/dist/mixer.js.map +1 -0
- package/dist/motion-scope.d.ts +4 -1
- package/dist/motion-scope.d.ts.map +1 -1
- package/dist/motion-scope.js +8 -3
- package/dist/motion-scope.js.map +1 -1
- package/dist/motion-states.d.ts +10 -5
- package/dist/motion-states.d.ts.map +1 -1
- package/dist/motion-states.js +55 -22
- package/dist/motion-states.js.map +1 -1
- package/dist/motion-value.d.ts +115 -0
- package/dist/motion-value.d.ts.map +1 -0
- package/dist/motion-value.js +437 -0
- package/dist/motion-value.js.map +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -2
- package/dist/path.js.map +1 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +31 -12
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +15 -4
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +46 -7
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +28 -2
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +78 -2
- package/dist/vue/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrame.java +53 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrameFollower.java +57 -0
- package/src/android-plugin.d.ts +27 -0
- package/src/animate.ts +99 -16
- package/src/drag.ts +67 -21
- package/src/easing.ts +14 -0
- package/src/engine.ts +158 -53
- package/src/gradient.ts +10 -4
- package/src/index.ts +18 -1
- package/src/keyboard.ts +83 -18
- package/src/mixer.ts +264 -0
- package/src/motion-scope.ts +12 -4
- package/src/motion-states.ts +79 -29
- package/src/motion-value.ts +563 -0
- package/src/path.ts +2 -1
- package/src/track.ts +37 -12
- package/src/transition.ts +69 -10
- package/src/types.ts +5 -0
- package/src/vue/index.ts +98 -1
package/src/android-plugin.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 () =>
|
|
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) =>
|
|
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
|
-
/**
|
|
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
|
|
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(
|
|
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 === '
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
/**
|
|
166
|
-
|
|
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:
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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
|
|
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 {
|