@rafaelaugusto/jolt 0.1.0-beta.11 → 0.1.0-beta.13
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 +42 -8
- package/dist/animate.d.ts +15 -3
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +52 -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/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +2 -0
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/frame.d.ts +3 -0
- package/dist/driver/frame.d.ts.map +1 -0
- package/dist/driver/frame.js +7 -0
- package/dist/driver/frame.js.map +1 -0
- package/dist/driver/index.d.ts +2 -0
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +2 -0
- package/dist/driver/ios.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 +20 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +166 -38
- 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/image-effect.d.ts +1 -0
- package/dist/image-effect.d.ts.map +1 -1
- package/dist/image-effect.js +10 -2
- package/dist/image-effect.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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 +6 -2
- package/dist/motion-scope.d.ts.map +1 -1
- package/dist/motion-scope.js +12 -4
- 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 +152 -0
- package/dist/motion-value.d.ts.map +1 -0
- package/dist/motion-value.js +635 -0
- package/dist/motion-value.js.map +1 -0
- package/dist/native/index.d.ts +6 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +3 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native-properties.d.ts +28 -0
- package/dist/native-properties.d.ts.map +1 -0
- package/dist/native-properties.js +154 -0
- package/dist/native-properties.js.map +1 -0
- package/dist/native-property-driver.d.ts +19 -0
- package/dist/native-property-driver.d.ts.map +1 -0
- package/dist/native-property-driver.js +69 -0
- package/dist/native-property-driver.js.map +1 -0
- package/dist/native-property.d.ts +35 -0
- package/dist/native-property.d.ts.map +1 -0
- package/dist/native-property.js +277 -0
- package/dist/native-property.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/playback-group.d.ts.map +1 -1
- package/dist/playback-group.js +17 -1
- package/dist/playback-group.js.map +1 -1
- package/dist/playback.d.ts +15 -0
- package/dist/playback.d.ts.map +1 -1
- package/dist/playback.js +23 -0
- package/dist/playback.js.map +1 -1
- package/dist/sequence.d.ts +24 -0
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +181 -19
- package/dist/sequence.js.map +1 -1
- package/dist/timed-animation.d.ts +38 -0
- package/dist/timed-animation.d.ts.map +1 -0
- package/dist/timed-animation.js +177 -0
- package/dist/timed-animation.js.map +1 -0
- 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/value-observers.d.ts +19 -0
- package/dist/value-observers.d.ts.map +1 -0
- package/dist/value-observers.js +111 -0
- package/dist/value-observers.js.map +1 -0
- package/dist/value-track.d.ts +13 -0
- package/dist/value-track.d.ts.map +1 -0
- package/dist/value-track.js +59 -0
- package/dist/value-track.js.map +1 -0
- 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 +6 -2
- 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 +64 -0
- package/src/animate.ts +106 -17
- package/src/drag.ts +67 -21
- package/src/driver/android.ts +2 -0
- package/src/driver/frame.ts +10 -0
- package/src/driver/index.ts +2 -0
- package/src/driver/ios.ts +2 -0
- package/src/easing.ts +14 -0
- package/src/engine.ts +181 -55
- package/src/gradient.ts +10 -4
- package/src/image-effect.ts +10 -2
- package/src/index.ts +23 -1
- package/src/ios-plugin.d.ts +33 -0
- package/src/keyboard.ts +83 -18
- package/src/mixer.ts +264 -0
- package/src/motion-scope.ts +17 -5
- package/src/motion-states.ts +79 -29
- package/src/motion-value.ts +795 -0
- package/src/native/index.ts +16 -0
- package/src/native-properties.ts +188 -0
- package/src/native-property-driver.ts +123 -0
- package/src/native-property.ts +353 -0
- package/src/path.ts +2 -1
- package/src/playback-group.ts +17 -1
- package/src/playback.ts +41 -0
- package/src/sequence.ts +209 -20
- package/src/timed-animation.ts +192 -0
- package/src/track.ts +37 -12
- package/src/transition.ts +69 -10
- package/src/types.ts +5 -0
- package/src/value-observers.ts +133 -0
- package/src/value-track.ts +71 -0
- package/src/vue/index.ts +98 -1
package/src/engine.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createClock,
|
|
3
|
+
registerPlayback,
|
|
3
4
|
validateProgress,
|
|
4
5
|
type PlaybackAnimation,
|
|
5
6
|
type PlaybackClock,
|
|
6
7
|
type PlaybackCommand,
|
|
7
8
|
} from './playback.js'
|
|
8
|
-
import type
|
|
9
|
+
import { Application, type View } from '@nativescript/core'
|
|
9
10
|
import { getDriver } from './driver/index.js'
|
|
10
11
|
import { playHaptics, type HapticOptions } from './haptics.js'
|
|
11
12
|
import { readValue, writeValue, type Property } from './properties.js'
|
|
@@ -13,6 +14,7 @@ import { createTrack, type Track } from './track.js'
|
|
|
13
14
|
import type { ResolvedTransition } from './transition.js'
|
|
14
15
|
import { isColorProperty } from './color.js'
|
|
15
16
|
import type { PreparedPath, PathProperty } from './path.js'
|
|
17
|
+
import { BASE, identity, MixHandle, mixFor, mixOf, mixesOf } from './mixer.js'
|
|
16
18
|
|
|
17
19
|
export interface Animation extends PromiseLike<boolean> {
|
|
18
20
|
/**
|
|
@@ -32,10 +34,17 @@ export interface Plan {
|
|
|
32
34
|
transition: ResolvedTransition
|
|
33
35
|
relative?: boolean
|
|
34
36
|
path?: PreparedPath
|
|
37
|
+
/** A track that is already sampled, like a motion value mapped onto a property. */
|
|
38
|
+
track?: () => Track
|
|
39
|
+
/** A named layer composed with the property's base instead of replacing it. */
|
|
40
|
+
layer?: string
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
interface Entry {
|
|
38
44
|
readonly track: Track
|
|
45
|
+
/** The property, or the property and its layer. */
|
|
46
|
+
readonly key: string
|
|
47
|
+
readonly layer?: string
|
|
39
48
|
readonly startedAt: number
|
|
40
49
|
readonly owner: ViewAnimation
|
|
41
50
|
handle: unknown
|
|
@@ -48,7 +57,7 @@ interface Entry {
|
|
|
48
57
|
|
|
49
58
|
const revisions = new WeakMap<View, Map<Property, symbol>>()
|
|
50
59
|
|
|
51
|
-
const running = new WeakMap<View, Map<
|
|
60
|
+
const running = new WeakMap<View, Map<string, Entry>>()
|
|
52
61
|
const arrivals = new WeakMap<Animation, Promise<unknown>>()
|
|
53
62
|
|
|
54
63
|
/** Internal forecast for starting a timeline step before another one arrives. */
|
|
@@ -101,7 +110,9 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
101
110
|
private readyListener: (() => void) | undefined
|
|
102
111
|
private readonly endless: boolean
|
|
103
112
|
private readonly timingListeners = new Set<() => void>()
|
|
113
|
+
private readonly playbackChanged: () => void
|
|
104
114
|
private timingChanged() {
|
|
115
|
+
this.playbackChanged()
|
|
105
116
|
for (const callback of [...this.timingListeners]) callback()
|
|
106
117
|
}
|
|
107
118
|
|
|
@@ -134,6 +145,7 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
134
145
|
if (this.settled) return
|
|
135
146
|
if (!this.started) {
|
|
136
147
|
this.pendingProgress = value
|
|
148
|
+
this.timingChanged()
|
|
137
149
|
return
|
|
138
150
|
}
|
|
139
151
|
this.seekTime(this.duration * value)
|
|
@@ -188,7 +200,7 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
188
200
|
entry.done = false
|
|
189
201
|
entry.finished = false
|
|
190
202
|
this.pending++
|
|
191
|
-
entriesOf(this.view).set(entry.
|
|
203
|
+
entriesOf(this.view).set(entry.key, entry)
|
|
192
204
|
this.playEntry(entry, position)
|
|
193
205
|
}
|
|
194
206
|
}
|
|
@@ -199,9 +211,9 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
199
211
|
this.timingChanged()
|
|
200
212
|
}
|
|
201
213
|
elapsed(entry: Entry, now: number) {
|
|
202
|
-
return this.
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
if (this.controlled) return this.time()
|
|
215
|
+
if (entry.handle instanceof MixHandle) return entry.handle.elapsed()
|
|
216
|
+
return getDriver().elapsed?.(entry.handle) ?? now - entry.startedAt
|
|
205
217
|
}
|
|
206
218
|
private time() {
|
|
207
219
|
return this.clock?.time() ?? 0
|
|
@@ -209,8 +221,10 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
209
221
|
private controlTracks(position: number, command: PlaybackCommand = 'seek') {
|
|
210
222
|
const driver = getDriver()
|
|
211
223
|
for (const entry of this.entries)
|
|
212
|
-
if (
|
|
213
|
-
|
|
224
|
+
if (entry.done) continue
|
|
225
|
+
else if (entry.handle instanceof MixHandle)
|
|
226
|
+
entry.handle.control(position, this._paused)
|
|
227
|
+
else driver.control?.(this.view, entry.handle, position, this._paused, command)
|
|
214
228
|
}
|
|
215
229
|
private scheduleArrival() {
|
|
216
230
|
this.cancelArrival?.()
|
|
@@ -244,21 +258,38 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
244
258
|
const initial = entry.generation === 0
|
|
245
259
|
const instance = ++entry.generation
|
|
246
260
|
const driver = getDriver()
|
|
247
|
-
entry.
|
|
248
|
-
|
|
249
|
-
entry.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
const startedAt = initial ? entry.startedAt : driver.now() - elapsed
|
|
262
|
+
const onEnd = (finished = true) => {
|
|
263
|
+
if (entry.done || instance !== entry.generation) return
|
|
264
|
+
entry.done = true
|
|
265
|
+
entry.finished = finished
|
|
266
|
+
entry.completedValue = readValue(this.view, entry.track.property)
|
|
267
|
+
forget(entry)
|
|
268
|
+
this.ended(finished)
|
|
269
|
+
}
|
|
270
|
+
const { property } = entry.track
|
|
271
|
+
const mix =
|
|
272
|
+
entry.layer !== undefined ? mixing(this.view, property) : mixOf(this.view, property)
|
|
273
|
+
entry.handle = mix
|
|
274
|
+
? mix.adopt(entry.layer ?? BASE, entry.track, startedAt, onEnd)
|
|
275
|
+
: driver.play(this.view, entry.track, startedAt, onEnd)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Moves a natively playing entry into its property's mix, without restarting it. */
|
|
279
|
+
rehome(entry: Entry) {
|
|
280
|
+
if (entry.done || entry.handle instanceof MixHandle) return
|
|
281
|
+
const elapsed = this.elapsed(entry, getDriver().now())
|
|
282
|
+
const handle = entry.handle
|
|
283
|
+
entry.generation++
|
|
284
|
+
getDriver().stop(this.view, handle)
|
|
285
|
+
this.playEntry(entry, elapsed)
|
|
286
|
+
}
|
|
287
|
+
private unloaded = () => {
|
|
288
|
+
// NativeScript unloads the root view when iOS enters the background.
|
|
289
|
+
// An explicitly paused owner can resume after that temporary unload.
|
|
290
|
+
if (this._paused && Application.suspended) return
|
|
291
|
+
this.stop()
|
|
260
292
|
}
|
|
261
|
-
private unloaded = () => this.stop()
|
|
262
293
|
|
|
263
294
|
constructor(
|
|
264
295
|
readonly view: View,
|
|
@@ -273,6 +304,18 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
273
304
|
!Number.isFinite(plan.transition.curve.duration)),
|
|
274
305
|
)
|
|
275
306
|
this.finished = new Promise((resolve) => (this.resolve = resolve))
|
|
307
|
+
this.playbackChanged = registerPlayback(this, () => ({
|
|
308
|
+
position: this.pendingProgress,
|
|
309
|
+
time:
|
|
310
|
+
this.pendingProgress === undefined
|
|
311
|
+
? this.time()
|
|
312
|
+
: this.pendingProgress * this.duration,
|
|
313
|
+
duration: this.duration,
|
|
314
|
+
paused: this._paused,
|
|
315
|
+
ready: this.ready,
|
|
316
|
+
settled: this.settled,
|
|
317
|
+
completed: this.settled && this.complete,
|
|
318
|
+
}))
|
|
276
319
|
setArrival(this, new Promise<void>((resolve) => (this.arrive = resolve)))
|
|
277
320
|
setArrivalTiming(this, {
|
|
278
321
|
read: () => ({
|
|
@@ -322,52 +365,78 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
322
365
|
const now = driver.now()
|
|
323
366
|
const entries = entriesOf(this.view)
|
|
324
367
|
|
|
325
|
-
const
|
|
326
|
-
const
|
|
368
|
+
const made = plans.map((plan) => {
|
|
369
|
+
const { property, keyframes, transition, relative, path, layer } = plan
|
|
370
|
+
const key = layer === undefined ? property : `${property}#${layer}`
|
|
371
|
+
const mixed = layer !== undefined || mixOf(this.view, property) !== undefined
|
|
372
|
+
const previous = entries.get(key)
|
|
327
373
|
let current: number
|
|
328
374
|
let velocity = 0
|
|
329
375
|
if (previous) {
|
|
330
376
|
const elapsed = elapsedOf(previous, now)
|
|
331
|
-
current =
|
|
377
|
+
current =
|
|
378
|
+
(mixed ? undefined : driver.presented(this.view, property)) ??
|
|
379
|
+
previous.track.valueAt(elapsed)
|
|
332
380
|
velocity = previous.owner.paused ? 0 : previous.track.velocityAt(elapsed)
|
|
333
381
|
release(previous, now, false)
|
|
382
|
+
} else if (mixed) {
|
|
383
|
+
current =
|
|
384
|
+
mixOf(this.view, property)?.rest(layer ?? BASE) ??
|
|
385
|
+
(layer === undefined ? readValue(this.view, property) : identity(property))
|
|
334
386
|
} else {
|
|
335
387
|
current =
|
|
336
388
|
(isColorProperty(property)
|
|
337
389
|
? driver.presented(this.view, property)
|
|
338
390
|
: undefined) ?? readValue(this.view, property)
|
|
339
391
|
}
|
|
340
|
-
|
|
341
|
-
return
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
392
|
+
const build = () => {
|
|
393
|
+
if (plan.track) return plan.track()
|
|
394
|
+
if (path)
|
|
395
|
+
return path.track(
|
|
396
|
+
property as PathProperty,
|
|
397
|
+
relative ? current : 0,
|
|
398
|
+
transition.delay,
|
|
399
|
+
)
|
|
400
|
+
return createTrack(
|
|
401
|
+
property,
|
|
402
|
+
keyframes.map((value) => {
|
|
403
|
+
if (value === null) return current
|
|
404
|
+
if (layer !== undefined || !relative || isColorProperty(property))
|
|
405
|
+
return value
|
|
406
|
+
return property === 'opacity' || property.startsWith('scale')
|
|
407
|
+
? current * value
|
|
408
|
+
: current + value
|
|
409
|
+
}),
|
|
410
|
+
transition,
|
|
411
|
+
velocity,
|
|
345
412
|
)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
keyframes.map((value) => {
|
|
349
|
-
if (value === null) return current
|
|
350
|
-
if (!relative || isColorProperty(property)) return value
|
|
351
|
-
return property === 'opacity' || property.startsWith('scale')
|
|
352
|
-
? current * value
|
|
353
|
-
: current + value
|
|
354
|
-
}),
|
|
355
|
-
transition,
|
|
356
|
-
velocity,
|
|
357
|
-
)
|
|
413
|
+
}
|
|
414
|
+
return { track: build(), key, layer }
|
|
358
415
|
})
|
|
359
|
-
|
|
360
|
-
|
|
416
|
+
const tracks = made.map(({ track }) => track)
|
|
417
|
+
|
|
418
|
+
for (const { track, layer } of made) {
|
|
419
|
+
const mix =
|
|
420
|
+
layer !== undefined
|
|
421
|
+
? mixing(this.view, track.property)
|
|
422
|
+
: mixOf(this.view, track.property)
|
|
423
|
+
if (!mix) writeValue(this.view, track.property, track.to)
|
|
424
|
+
else if (track.end === 0) mix.setRest(layer ?? BASE, track.to)
|
|
425
|
+
}
|
|
361
426
|
driver.sync?.(this.view)
|
|
362
427
|
|
|
363
|
-
for (const track of
|
|
364
|
-
let tokens = revisions.get(this.view)
|
|
365
|
-
if (!tokens) revisions.set(this.view, (tokens = new Map()))
|
|
428
|
+
for (const { track, key, layer } of made) {
|
|
366
429
|
const token = Symbol(track.property)
|
|
367
|
-
|
|
430
|
+
if (layer === undefined) {
|
|
431
|
+
let tokens = revisions.get(this.view)
|
|
432
|
+
if (!tokens) revisions.set(this.view, (tokens = new Map()))
|
|
433
|
+
tokens.set(track.property, token)
|
|
434
|
+
}
|
|
368
435
|
if (track.end === 0) continue
|
|
369
436
|
const entry: Entry = {
|
|
370
437
|
track,
|
|
438
|
+
key,
|
|
439
|
+
layer,
|
|
371
440
|
startedAt: now,
|
|
372
441
|
owner: this,
|
|
373
442
|
handle: undefined,
|
|
@@ -376,7 +445,7 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
376
445
|
generation: 0,
|
|
377
446
|
token,
|
|
378
447
|
}
|
|
379
|
-
entries.set(
|
|
448
|
+
entries.set(key, entry)
|
|
380
449
|
this.entries.push(entry)
|
|
381
450
|
this.pending++
|
|
382
451
|
}
|
|
@@ -405,8 +474,8 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
405
474
|
}
|
|
406
475
|
|
|
407
476
|
private waitForLoad(plans: readonly Plan[]) {
|
|
408
|
-
for (const { property, keyframes, relative } of plans) {
|
|
409
|
-
if (relative && !isColorProperty(property)) continue
|
|
477
|
+
for (const { property, keyframes, relative, layer } of plans) {
|
|
478
|
+
if (layer !== undefined || (relative && !isColorProperty(property))) continue
|
|
410
479
|
const first = keyframes[0]
|
|
411
480
|
if (first !== null && first !== undefined) writeValue(this.view, property, first)
|
|
412
481
|
}
|
|
@@ -438,6 +507,8 @@ export class ViewAnimation implements PlaybackAnimation {
|
|
|
438
507
|
|
|
439
508
|
/** Current native presentation when available, otherwise the active curve or model. */
|
|
440
509
|
export function currentValue(view: View, property: Property): number {
|
|
510
|
+
const mix = mixOf(view, property)
|
|
511
|
+
if (mix) return mix.value()
|
|
441
512
|
const entry = running.get(view)?.get(property)
|
|
442
513
|
if (!entry) return readValue(view, property)
|
|
443
514
|
const driver = getDriver()
|
|
@@ -465,19 +536,69 @@ export function captureValue(view: View, property: Property): () => number | und
|
|
|
465
536
|
}
|
|
466
537
|
}
|
|
467
538
|
|
|
468
|
-
/** Stops whatever is animating these properties of the view, or all of them. */
|
|
539
|
+
/** Stops whatever is animating these properties of the view, or all of them, layers included. */
|
|
469
540
|
export function stopView(view: View, properties?: readonly Property[]) {
|
|
470
541
|
const tokens = revisions.get(view)
|
|
471
542
|
for (const property of properties ?? tokens?.keys() ?? [])
|
|
472
543
|
tokens?.set(property, Symbol(property))
|
|
544
|
+
for (const mix of mixesOf(view))
|
|
545
|
+
if (!properties || properties.includes(mix.property)) mix.freeze()
|
|
473
546
|
const entries = running.get(view)
|
|
474
547
|
if (!entries) return
|
|
475
548
|
const now = getDriver().now()
|
|
476
|
-
for (const
|
|
477
|
-
if (!properties || properties.includes(property))
|
|
549
|
+
for (const entry of [...entries.values()]) {
|
|
550
|
+
if (!properties || properties.includes(entry.track.property))
|
|
551
|
+
release(entry, now, true)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Stops the regular animations of these properties and leaves their layers running. */
|
|
556
|
+
export function stopBase(view: View, properties: readonly Property[]) {
|
|
557
|
+
const tokens = revisions.get(view)
|
|
558
|
+
for (const property of properties) tokens?.set(property, Symbol(property))
|
|
559
|
+
const now = getDriver().now()
|
|
560
|
+
for (const property of properties) {
|
|
561
|
+
const entry = running.get(view)?.get(property)
|
|
562
|
+
if (entry) release(entry, now, true)
|
|
478
563
|
}
|
|
479
564
|
}
|
|
480
565
|
|
|
566
|
+
/** Stops one layer and takes away what it adds, also after it finished. */
|
|
567
|
+
export function stopLayer(view: View, layer: string, properties?: readonly Property[]) {
|
|
568
|
+
const now = getDriver().now()
|
|
569
|
+
for (const entry of [...(running.get(view)?.values() ?? [])])
|
|
570
|
+
if (
|
|
571
|
+
entry.layer === layer &&
|
|
572
|
+
(!properties || properties.includes(entry.track.property))
|
|
573
|
+
)
|
|
574
|
+
release(entry, now, true)
|
|
575
|
+
for (const mix of mixesOf(view))
|
|
576
|
+
if (!properties || properties.includes(mix.property))
|
|
577
|
+
mix.setRest(layer, identity(mix.property))
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** Writes a property's base value, under its layers when it has any. */
|
|
581
|
+
export function setBase(view: View, property: Property, value: number) {
|
|
582
|
+
const mix = mixOf(view, property)
|
|
583
|
+
if (mix) mix.setRest(BASE, value)
|
|
584
|
+
else writeValue(view, property, value)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** A property's base value, without what its layers add. */
|
|
588
|
+
export function baseValue(view: View, property: Property): number {
|
|
589
|
+
return mixOf(view, property)?.rest(BASE) ?? readValue(view, property)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** The property's mix, created on its first layer; a base playing natively moves into it. */
|
|
593
|
+
function mixing(view: View, property: Property) {
|
|
594
|
+
const existing = mixOf(view, property)
|
|
595
|
+
if (existing) return existing
|
|
596
|
+
const mix = mixFor(view, property)
|
|
597
|
+
const base = running.get(view)?.get(property)
|
|
598
|
+
if (base) base.owner.rehome(base)
|
|
599
|
+
return mix
|
|
600
|
+
}
|
|
601
|
+
|
|
481
602
|
function entriesOf(view: View) {
|
|
482
603
|
let entries = running.get(view)
|
|
483
604
|
if (!entries) running.set(view, (entries = new Map()))
|
|
@@ -486,7 +607,7 @@ function entriesOf(view: View) {
|
|
|
486
607
|
|
|
487
608
|
function forget(entry: Entry) {
|
|
488
609
|
const entries = running.get(entry.owner.view)
|
|
489
|
-
if (entries?.get(entry.
|
|
610
|
+
if (entries?.get(entry.key) === entry) entries.delete(entry.key)
|
|
490
611
|
}
|
|
491
612
|
|
|
492
613
|
function release(entry: Entry, now: number, commit: boolean) {
|
|
@@ -495,6 +616,11 @@ function release(entry: Entry, now: number, commit: boolean) {
|
|
|
495
616
|
const { property } = entry.track
|
|
496
617
|
entry.done = true
|
|
497
618
|
forget(entry)
|
|
619
|
+
if (entry.handle instanceof MixHandle) {
|
|
620
|
+
entry.handle.mix.detach(entry.layer ?? BASE, commit)
|
|
621
|
+
entry.owner.ended(false)
|
|
622
|
+
return
|
|
623
|
+
}
|
|
498
624
|
if (commit) {
|
|
499
625
|
const value =
|
|
500
626
|
driver.presented(view, property) ?? entry.track.valueAt(elapsedOf(entry, now))
|
package/src/gradient.ts
CHANGED
|
@@ -345,10 +345,16 @@ export function gradientPoints(
|
|
|
345
345
|
const sin = Math.sin(radians)
|
|
346
346
|
const cos = Math.cos(radians)
|
|
347
347
|
if (type === 'linear') {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
|
|
348
|
+
// Core Animation keeps an axial gradient's bands square to its line in unit space, so
|
|
349
|
+
// on a box that isn't square the line leans to keep them square to `angle` on screen.
|
|
350
|
+
const ux = height > 0 ? sin / height : 0
|
|
351
|
+
const uy = width > 0 ? -cos / width : 0
|
|
352
|
+
const squared = ux * ux + uy * uy
|
|
353
|
+
const reach = squared > 0 ? (Math.abs(ux) + Math.abs(uy)) / (2 * squared) : 0
|
|
354
|
+
return {
|
|
355
|
+
start: { x: 0.5 - ux * reach, y: 0.5 - uy * reach },
|
|
356
|
+
end: { x: 0.5 + ux * reach, y: 0.5 + uy * reach },
|
|
357
|
+
}
|
|
352
358
|
}
|
|
353
359
|
const center = { x: frame.x, y: frame.y }
|
|
354
360
|
const shorter = Math.min(width, height)
|
package/src/image-effect.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface ImageEffectDiagnostics {
|
|
|
43
43
|
export interface ImageEffect extends Animation {
|
|
44
44
|
readonly effect: ImageEffectName
|
|
45
45
|
readonly paused: boolean
|
|
46
|
+
readonly duration: number
|
|
46
47
|
/** Finite progress; distortion rejects seeking because it follows native touch input. */
|
|
47
48
|
progress: number
|
|
48
49
|
readonly diagnostics: Readonly<ImageEffectDiagnostics>
|
|
@@ -157,7 +158,7 @@ class Effect implements ImageEffect {
|
|
|
157
158
|
for (const event of ['loaded', 'layoutChanged']) image.on(event, this.schedule)
|
|
158
159
|
for (const event of ['imageSourceChange', 'stretchChange'])
|
|
159
160
|
image.on(event, this.changed)
|
|
160
|
-
image.on('unloaded', this.
|
|
161
|
+
image.on('unloaded', this.unloaded)
|
|
161
162
|
image.style.on('visibilityChange', this.visible)
|
|
162
163
|
for (const event of surfaceChanges) image.style.on(event, this.changed)
|
|
163
164
|
Application.on(Application.suspendEvent, this.suspend)
|
|
@@ -178,6 +179,9 @@ class Effect implements ImageEffect {
|
|
|
178
179
|
get paused() {
|
|
179
180
|
return this.userPaused
|
|
180
181
|
}
|
|
182
|
+
get duration() {
|
|
183
|
+
return this.effect === 'distortion' ? Infinity : this.options.duration
|
|
184
|
+
}
|
|
181
185
|
get progress() {
|
|
182
186
|
return this.renderer?.progress ?? this.position
|
|
183
187
|
}
|
|
@@ -240,7 +244,7 @@ class Effect implements ImageEffect {
|
|
|
240
244
|
for (const event of ['loaded', 'layoutChanged']) this.image.off(event, this.schedule)
|
|
241
245
|
for (const event of ['imageSourceChange', 'stretchChange'])
|
|
242
246
|
this.image.off(event, this.changed)
|
|
243
|
-
this.image.off('unloaded', this.
|
|
247
|
+
this.image.off('unloaded', this.unloaded)
|
|
244
248
|
this.image.style.off('visibilityChange', this.visible)
|
|
245
249
|
for (const event of surfaceChanges) this.image.style.off(event, this.changed)
|
|
246
250
|
Application.off(Application.suspendEvent, this.suspend)
|
|
@@ -272,6 +276,10 @@ class Effect implements ImageEffect {
|
|
|
272
276
|
this.suspended = true
|
|
273
277
|
this.renderer?.pause()
|
|
274
278
|
}
|
|
279
|
+
private unloaded = () => {
|
|
280
|
+
if (Application.suspended && this.userPaused) return
|
|
281
|
+
this.stop()
|
|
282
|
+
}
|
|
275
283
|
private resume = () => {
|
|
276
284
|
this.suspended = false
|
|
277
285
|
this.schedule()
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
export { timeline } from './sequence.js'
|
|
2
|
+
export type { TimelineAnimation, TimelineStep, PlayableAnimation } from './sequence.js'
|
|
3
|
+
export { playbackProgress } from './motion-value.js'
|
|
1
4
|
export { animate, decay, set, stop } from './animate.js'
|
|
5
|
+
export { bind, motionValue, scrollValue } from './motion-value.js'
|
|
6
|
+
export type {
|
|
7
|
+
Bindings,
|
|
8
|
+
DerivedColor,
|
|
9
|
+
DerivedValue,
|
|
10
|
+
MapOptions,
|
|
11
|
+
MotionValue,
|
|
12
|
+
ScrollValue,
|
|
13
|
+
ValueAnimationOptions,
|
|
14
|
+
} from './motion-value.js'
|
|
15
|
+
export type { SubscribeOptions, ValueListener } from './value-observers.js'
|
|
2
16
|
export { sequence } from './sequence.js'
|
|
3
17
|
export type { Step, SequenceOptions, SequencePosition } from './sequence.js'
|
|
4
18
|
export { press } from './press.js'
|
|
5
19
|
export { interactionSurface } from './interaction-surface.js'
|
|
6
20
|
export type { InteractionSurface } from './interaction-surface.js'
|
|
7
21
|
export { drag } from './drag.js'
|
|
8
|
-
export type {
|
|
22
|
+
export type {
|
|
23
|
+
Drag,
|
|
24
|
+
DragOptions,
|
|
25
|
+
DragValueOptions,
|
|
26
|
+
DragPoint,
|
|
27
|
+
DragEvent,
|
|
28
|
+
DragRelease,
|
|
29
|
+
} from './drag.js'
|
|
9
30
|
export { scrollLinked } from './scroll-linked.js'
|
|
10
31
|
export type { ScrollLinkedOptions } from './scroll-linked.js'
|
|
11
32
|
export { animateItems } from './items.js'
|
|
@@ -56,6 +77,7 @@ export type { Preset, PresetName, Presets } from './presets.js'
|
|
|
56
77
|
export { origamiSpring, springs } from './transition.js'
|
|
57
78
|
export type {
|
|
58
79
|
OrigamiSpring,
|
|
80
|
+
RepeatDelayRange,
|
|
59
81
|
Spring,
|
|
60
82
|
SpringName,
|
|
61
83
|
Timing,
|
package/src/ios-plugin.d.ts
CHANGED
|
@@ -79,3 +79,36 @@ declare class JoltPhotoZoom extends NSObject {
|
|
|
79
79
|
layout(): void
|
|
80
80
|
stop(): void
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
declare class JoltPropertySink extends NSObject {
|
|
84
|
+
static scalarTargetKeyPath(target: NSObject, keyPath: string): JoltPropertySink
|
|
85
|
+
readonly dimensions: number
|
|
86
|
+
readonly alive: boolean
|
|
87
|
+
readonly compiled: boolean
|
|
88
|
+
read(): NSArray<number>
|
|
89
|
+
write(values: NSArray<number>): void
|
|
90
|
+
dispose(): void
|
|
91
|
+
}
|
|
92
|
+
declare class JoltPropertyPlayer extends NSObject {
|
|
93
|
+
static startSinkTimesValuesFinalValueDurationDelayRepeatReverseCompletion(
|
|
94
|
+
sink: JoltPropertySink,
|
|
95
|
+
times: NSArray<number>,
|
|
96
|
+
values: NSArray<number>,
|
|
97
|
+
finalValue: NSArray<number>,
|
|
98
|
+
duration: number,
|
|
99
|
+
delay: number,
|
|
100
|
+
repeat: number,
|
|
101
|
+
reverse: boolean,
|
|
102
|
+
completion: (completed: boolean) => void,
|
|
103
|
+
): JoltPropertyPlayer
|
|
104
|
+
readonly elapsed: number
|
|
105
|
+
readonly frames: number
|
|
106
|
+
static activeCount(): number
|
|
107
|
+
controlPaused(time: number, paused: boolean): void
|
|
108
|
+
cancel(): void
|
|
109
|
+
}
|
|
110
|
+
declare class JoltReferenceProperties extends NSObject {
|
|
111
|
+
static mapCoordinate(target: NSObject): JoltPropertySink
|
|
112
|
+
static mapCamera(target: NSObject): JoltPropertySink
|
|
113
|
+
static scenePose(target: NSObject): JoltPropertySink
|
|
114
|
+
}
|
package/src/keyboard.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Application, Utils, isAndroid, isIOS, type View } from '@nativescript/core'
|
|
2
|
-
import { stop, toView } from './animate.js'
|
|
2
|
+
import { animate, stop, toView } from './animate.js'
|
|
3
3
|
import type { EasingFunction, EasingName } from './easing.js'
|
|
4
|
-
import type { Spring, Transition } from './transition.js'
|
|
4
|
+
import type { Spring, Transition, Tween } from './transition.js'
|
|
5
5
|
import type { Target } from './types.js'
|
|
6
6
|
|
|
7
7
|
export interface KeyboardOptions {
|
|
@@ -30,6 +30,12 @@ const iosKeyboard: Spring = {
|
|
|
30
30
|
damping: 2 * Math.sqrt(3000),
|
|
31
31
|
}
|
|
32
32
|
const iosCurves: readonly EasingName[] = ['easeInOut', 'easeIn', 'easeOut', 'linear']
|
|
33
|
+
// Before Android 11 nothing reports the keyboard's timing. Measured on an Android 9 emulator,
|
|
34
|
+
// its window fades and slides into place in about 150 ms, decelerating, and leaves as quickly.
|
|
35
|
+
const LEGACY_DURATION = 150
|
|
36
|
+
const legacyShow: Tween = { duration: LEGACY_DURATION, ease: 'quadOut' }
|
|
37
|
+
const legacyHide: Tween = { duration: LEGACY_DURATION, ease: 'quadIn' }
|
|
38
|
+
const legacy = () => isAndroid && android.os.Build.VERSION.SDK_INT < 30
|
|
33
39
|
const hidden: KeyboardState = Object.freeze({
|
|
34
40
|
visible: false,
|
|
35
41
|
height: 0,
|
|
@@ -109,9 +115,11 @@ function watchIOS() {
|
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
function watchAndroid() {
|
|
112
|
-
if (android.os.Build.VERSION.SDK_INT < 30) return undefined
|
|
113
118
|
const density = Utils.layout.getDisplayDensity()
|
|
114
|
-
let observer:
|
|
119
|
+
let observer:
|
|
120
|
+
| org.rafaelaugusto.jolt.KeyboardObserver
|
|
121
|
+
| org.rafaelaugusto.jolt.KeyboardFrame
|
|
122
|
+
| undefined
|
|
115
123
|
let stopped = false
|
|
116
124
|
const attach = () => {
|
|
117
125
|
Application.off(Application.launchEvent, attach)
|
|
@@ -121,19 +129,31 @@ function watchAndroid() {
|
|
|
121
129
|
const root = activity?.getWindow()?.getDecorView()
|
|
122
130
|
if (!root) return
|
|
123
131
|
try {
|
|
124
|
-
observer =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
),
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
observer = legacy()
|
|
133
|
+
? new org.rafaelaugusto.jolt.KeyboardFrame(
|
|
134
|
+
root,
|
|
135
|
+
new org.rafaelaugusto.jolt.KeyboardFrame.Listener({
|
|
136
|
+
onChange: (height) =>
|
|
137
|
+
update(
|
|
138
|
+
height / density,
|
|
139
|
+
LEGACY_DURATION,
|
|
140
|
+
height ? legacyShow : legacyHide,
|
|
141
|
+
),
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
: new org.rafaelaugusto.jolt.KeyboardObserver(
|
|
145
|
+
root,
|
|
146
|
+
new org.rafaelaugusto.jolt.KeyboardObserver.Listener({
|
|
147
|
+
onChange: (height, duration, curve) =>
|
|
148
|
+
update(
|
|
149
|
+
height / density,
|
|
150
|
+
duration,
|
|
151
|
+
duration
|
|
152
|
+
? { duration, ease: curve ? sampled(curve) : 'linear' }
|
|
153
|
+
: { duration: 0 },
|
|
154
|
+
),
|
|
155
|
+
}),
|
|
156
|
+
)
|
|
137
157
|
} catch {
|
|
138
158
|
// A JS-only update cannot install the native plugin.
|
|
139
159
|
return
|
|
@@ -241,7 +261,7 @@ function followIOS(view: View, offset: number) {
|
|
|
241
261
|
}
|
|
242
262
|
|
|
243
263
|
function followAndroid(view: View, offset: number) {
|
|
244
|
-
if (
|
|
264
|
+
if (legacy()) return followLegacy(view, offset)
|
|
245
265
|
const density = Utils.layout.getDisplayDensity()
|
|
246
266
|
let disposed = false
|
|
247
267
|
let follower: org.rafaelaugusto.jolt.KeyboardFollower | undefined
|
|
@@ -283,6 +303,51 @@ function followAndroid(view: View, offset: number) {
|
|
|
283
303
|
return cleanup
|
|
284
304
|
}
|
|
285
305
|
|
|
306
|
+
/** Android 7 to 10: no frame-by-frame progress, so the view animates with the keyboard's usual timing. */
|
|
307
|
+
function followLegacy(view: View, offset: number) {
|
|
308
|
+
const density = Utils.layout.getDisplayDensity()
|
|
309
|
+
let disposed = false
|
|
310
|
+
let follower: org.rafaelaugusto.jolt.KeyboardFrameFollower | undefined
|
|
311
|
+
const attach = () => {
|
|
312
|
+
if (disposed || follower) return
|
|
313
|
+
const native = view.nativeViewProtected as android.view.View | undefined
|
|
314
|
+
if (!native) return
|
|
315
|
+
try {
|
|
316
|
+
follower = new org.rafaelaugusto.jolt.KeyboardFrameFollower(
|
|
317
|
+
native,
|
|
318
|
+
offset * density,
|
|
319
|
+
new org.rafaelaugusto.jolt.KeyboardFrameFollower.Listener({
|
|
320
|
+
onMove: (from, to, keyboard) => {
|
|
321
|
+
stop(view, ['y'])
|
|
322
|
+
view.translateY = from / density
|
|
323
|
+
animate(view, { y: to / density }, keyboard ? legacyShow : legacyHide)
|
|
324
|
+
},
|
|
325
|
+
}),
|
|
326
|
+
)
|
|
327
|
+
} catch {
|
|
328
|
+
// A JS-only update cannot install the native plugin. Leave the platform's resize in place.
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
follower.attach()
|
|
332
|
+
}
|
|
333
|
+
const cleanup = () => {
|
|
334
|
+
if (disposed) return
|
|
335
|
+
disposed = true
|
|
336
|
+
view.off('loaded', attach)
|
|
337
|
+
view.off('unloaded', cleanup)
|
|
338
|
+
if (follower) {
|
|
339
|
+
follower.detach()
|
|
340
|
+
follower = undefined
|
|
341
|
+
stop(view, ['y'])
|
|
342
|
+
view.translateY = 0
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
view.on('loaded', attach)
|
|
346
|
+
view.on('unloaded', cleanup)
|
|
347
|
+
if (view.isLoaded) attach()
|
|
348
|
+
return cleanup
|
|
349
|
+
}
|
|
350
|
+
|
|
286
351
|
/** The runtime hands NSNumber values over as plain numbers. */
|
|
287
352
|
function numberIn(info: NSDictionary<unknown, unknown>, key: string): number {
|
|
288
353
|
const value = info.objectForKey(key) as number | NSNumber | null | undefined
|