@rafaelaugusto/jolt 0.1.0-beta.15 → 0.1.0-beta.16
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 +3 -1
- package/dist/drag.js +1 -1
- 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/ios-scroll-delegate.d.ts +4 -0
- package/dist/driver/ios-scroll-delegate.d.ts.map +1 -0
- package/dist/driver/ios-scroll-delegate.js +26 -0
- package/dist/driver/ios-scroll-delegate.js.map +1 -0
- 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/gesture-native.d.ts +27 -0
- package/dist/gesture-native.d.ts.map +1 -0
- package/dist/gesture-native.js +55 -0
- package/dist/gesture-native.js.map +1 -0
- package/dist/gestures/index.d.ts +65 -0
- package/dist/gestures/index.d.ts.map +1 -0
- package/dist/gestures/index.js +251 -0
- package/dist/gestures/index.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/live-spring-driver.d.ts +52 -0
- package/dist/live-spring-driver.d.ts.map +1 -0
- package/dist/live-spring-driver.js +104 -0
- package/dist/live-spring-driver.js.map +1 -0
- package/dist/mixer.d.ts +6 -0
- package/dist/mixer.d.ts.map +1 -1
- package/dist/mixer.js +41 -1
- package/dist/mixer.js.map +1 -1
- package/dist/motion-value.d.ts +34 -2
- package/dist/motion-value.d.ts.map +1 -1
- package/dist/motion-value.js +277 -10
- package/dist/motion-value.js.map +1 -1
- package/dist/value-observers.d.ts +3 -1
- package/dist/value-observers.d.ts.map +1 -1
- package/dist/value-observers.js +10 -2
- package/dist/value-observers.js.map +1 -1
- package/package.json +5 -1
- package/platforms/android/include.gradle +9 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/GestureSession.java +193 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/GestureSurface.java +53 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/LiveSpring.java +270 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/SpringState.java +39 -0
- package/platforms/ios/src/JoltGestureSession.h +17 -0
- package/platforms/ios/src/JoltGestureSession.m +217 -0
- package/platforms/ios/src/JoltLiveSpring.h +28 -0
- package/platforms/ios/src/JoltLiveSpring.m +236 -0
- package/platforms/ios/src/JoltSpringState.h +35 -0
- package/platforms/ios/src/module.modulemap +2 -0
- package/src/android-plugin.d.ts +59 -0
- package/src/drag.ts +1 -1
- package/src/driver/android.ts +2 -0
- package/src/driver/ios-scroll-delegate.ts +23 -0
- package/src/driver/ios.ts +2 -0
- package/src/gesture-native.ts +124 -0
- package/src/gestures/index.ts +350 -0
- package/src/index.ts +3 -1
- package/src/ios-plugin.d.ts +58 -0
- package/src/live-spring-driver.ts +181 -0
- package/src/mixer.ts +43 -1
- package/src/motion-value.ts +318 -11
- package/src/value-observers.ts +12 -2
package/src/motion-value.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Application,
|
|
2
3
|
Color,
|
|
3
4
|
ScrollView,
|
|
4
5
|
type EventData,
|
|
@@ -13,7 +14,16 @@ import {
|
|
|
13
14
|
validateColorTarget,
|
|
14
15
|
type ColorValue,
|
|
15
16
|
} from './color.js'
|
|
16
|
-
import { reducedMotion } from './config.js'
|
|
17
|
+
import { onReducedMotion, reducedMotion, timeScale } from './config.js'
|
|
18
|
+
import { springPhysics, type SpringParams } from './spring.js'
|
|
19
|
+
import { mixFor } from './mixer.js'
|
|
20
|
+
import {
|
|
21
|
+
liveSpringDriver,
|
|
22
|
+
syncLiveSprings,
|
|
23
|
+
type LiveNode,
|
|
24
|
+
type LiveMapping,
|
|
25
|
+
type LiveBinding,
|
|
26
|
+
} from './live-spring-driver.js'
|
|
17
27
|
import {
|
|
18
28
|
clampDecoration,
|
|
19
29
|
isDecorationProperty,
|
|
@@ -22,7 +32,7 @@ import {
|
|
|
22
32
|
import { getDriver } from './driver/index.js'
|
|
23
33
|
import { setBase, stopBase, ViewAnimation, type Animation, type Plan } from './engine.js'
|
|
24
34
|
import { linear } from './easing.js'
|
|
25
|
-
import { isProperty, type Property } from './properties.js'
|
|
35
|
+
import { isProperty, type NumericProperty, type Property } from './properties.js'
|
|
26
36
|
import { createTrack, instantTrack, type Track } from './track.js'
|
|
27
37
|
import {
|
|
28
38
|
resolveTransition,
|
|
@@ -89,6 +99,7 @@ interface Binding {
|
|
|
89
99
|
readonly root: Root
|
|
90
100
|
readonly read: (value: number) => number
|
|
91
101
|
animation?: ViewAnimation
|
|
102
|
+
cleanup?: () => void
|
|
92
103
|
sync(): void
|
|
93
104
|
}
|
|
94
105
|
|
|
@@ -110,6 +121,7 @@ class Derived {
|
|
|
110
121
|
private readonly source: Root | undefined,
|
|
111
122
|
readonly read: (value: number) => number,
|
|
112
123
|
readonly color: boolean,
|
|
124
|
+
readonly mappings: readonly LiveMapping[] = [],
|
|
113
125
|
) {}
|
|
114
126
|
|
|
115
127
|
get root(): Root {
|
|
@@ -132,15 +144,36 @@ class Derived {
|
|
|
132
144
|
): Derived {
|
|
133
145
|
if (this.color) throw new TypeError('A color cannot be mapped again.')
|
|
134
146
|
const map = mapping(input, output, options)
|
|
147
|
+
if (this.root instanceof SpringRoot && map.color)
|
|
148
|
+
throw new TypeError(
|
|
149
|
+
'Live spring bindings currently support numeric transforms and opacity.',
|
|
150
|
+
)
|
|
135
151
|
const read = this.read
|
|
136
|
-
return new Derived(
|
|
152
|
+
return new Derived(
|
|
153
|
+
this.root,
|
|
154
|
+
(value) => map.read(read(value)),
|
|
155
|
+
map.color,
|
|
156
|
+
map.color
|
|
157
|
+
? []
|
|
158
|
+
: [
|
|
159
|
+
...this.mappings,
|
|
160
|
+
{
|
|
161
|
+
input: [...map.input],
|
|
162
|
+
output: [...map.numbers],
|
|
163
|
+
clamp: options?.clamp !== false,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
)
|
|
137
167
|
}
|
|
138
168
|
}
|
|
139
169
|
|
|
140
170
|
class Root extends Derived {
|
|
141
171
|
readonly bindings = new Set<Binding>()
|
|
142
172
|
readonly listeners = new Set<() => void>()
|
|
143
|
-
readonly observers = new ValueObservers(
|
|
173
|
+
readonly observers = new ValueObservers(
|
|
174
|
+
(time) => this.raw(time),
|
|
175
|
+
() => this.running,
|
|
176
|
+
)
|
|
144
177
|
private readonly rests = new Map<string, number>()
|
|
145
178
|
private readonly runs = new Map<string, Run>()
|
|
146
179
|
private presentation: Presentation | undefined
|
|
@@ -219,7 +252,7 @@ class Root extends Derived {
|
|
|
219
252
|
}
|
|
220
253
|
|
|
221
254
|
/** Plays a track sampled in the value's own units, such as a drag release. */
|
|
222
|
-
|
|
255
|
+
playTrack(track: Track): PlaybackAnimation {
|
|
223
256
|
this.halt('')
|
|
224
257
|
return track.end === 0 ? this.jump(track.to, '') : this.start(track, 0, 1, '')
|
|
225
258
|
}
|
|
@@ -435,6 +468,205 @@ class Root extends Derived {
|
|
|
435
468
|
}
|
|
436
469
|
}
|
|
437
470
|
|
|
471
|
+
export interface SpringValueOptions extends SpringParams {
|
|
472
|
+
/** Initial follower position. Defaults to the source's current value. */
|
|
473
|
+
initial?: number
|
|
474
|
+
/** Absolute source units. Defaults to 0.001. */
|
|
475
|
+
restDelta?: number
|
|
476
|
+
/** Source units per second. Defaults to 0.01. */
|
|
477
|
+
restSpeed?: number
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** A retained native follower. Settling does not dispose its source subscription. */
|
|
481
|
+
export interface SpringValue extends DerivedValue {
|
|
482
|
+
readonly disposed: boolean
|
|
483
|
+
readonly moving: boolean
|
|
484
|
+
readonly paused: boolean
|
|
485
|
+
/** Hold position and velocity while retaining the current source target. */
|
|
486
|
+
pause(): void
|
|
487
|
+
/** Resume from the held state toward the current source. */
|
|
488
|
+
play(): void
|
|
489
|
+
/** Current velocity in source units per second. */
|
|
490
|
+
getVelocity(): number
|
|
491
|
+
dispose(): void
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
class SpringRoot extends Root {
|
|
495
|
+
readonly node: LiveNode
|
|
496
|
+
private releaseSource: () => void = () => {}
|
|
497
|
+
private releaseReduced: () => void = () => {}
|
|
498
|
+
private ended = false
|
|
499
|
+
private held = false
|
|
500
|
+
private background = false
|
|
501
|
+
private suspend = () => {
|
|
502
|
+
this.background = true
|
|
503
|
+
this.updatePause()
|
|
504
|
+
}
|
|
505
|
+
private resume = () => {
|
|
506
|
+
this.background = false
|
|
507
|
+
this.updatePause()
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
constructor(source: Derived, options: SpringValueOptions) {
|
|
511
|
+
const initial = options.initial ?? (source.get() as number)
|
|
512
|
+
const instant = options.duration === 0
|
|
513
|
+
const params = springPhysics(instant ? { ...options, duration: undefined } : options)
|
|
514
|
+
const speed = timeScale()
|
|
515
|
+
const physics = {
|
|
516
|
+
stiffness: params.stiffness * speed * speed,
|
|
517
|
+
damping: params.damping * speed,
|
|
518
|
+
mass: params.mass,
|
|
519
|
+
restDelta: options.restDelta ?? 0.001,
|
|
520
|
+
restSpeed: options.restSpeed ?? 0.01,
|
|
521
|
+
}
|
|
522
|
+
for (const [key, value] of Object.entries(physics))
|
|
523
|
+
if (!Number.isFinite(value) || (key === 'damping' ? value < 0 : value <= 0))
|
|
524
|
+
throw new RangeError(`Invalid springValue ${key}: ${value}.`)
|
|
525
|
+
if (
|
|
526
|
+
options.duration !== undefined &&
|
|
527
|
+
(!Number.isFinite(options.duration) || options.duration < 0)
|
|
528
|
+
)
|
|
529
|
+
throw new RangeError('springValue duration must be finite and nonnegative.')
|
|
530
|
+
if (options.bounce !== undefined && !Number.isFinite(options.bounce))
|
|
531
|
+
throw new RangeError('springValue bounce must be finite.')
|
|
532
|
+
super(initial)
|
|
533
|
+
this.node = liveSpringDriver().create(initial, physics, instant || reducedMotion())
|
|
534
|
+
const root = source.root
|
|
535
|
+
try {
|
|
536
|
+
let update: () => void
|
|
537
|
+
if (root instanceof SpringRoot) {
|
|
538
|
+
if (root.disposed) throw new Error('Cannot follow a disposed spring value.')
|
|
539
|
+
this.node.follow(root.node, source.mappings)
|
|
540
|
+
update = () => this.notify()
|
|
541
|
+
} else {
|
|
542
|
+
const playback = valuePlayback(source as unknown as DerivedValue)
|
|
543
|
+
update = () => {
|
|
544
|
+
if (!root.running) {
|
|
545
|
+
this.node.update({
|
|
546
|
+
value: source.get() as number,
|
|
547
|
+
running: false,
|
|
548
|
+
elapsed: 0,
|
|
549
|
+
duration: 0,
|
|
550
|
+
delay: 0,
|
|
551
|
+
repeat: 0,
|
|
552
|
+
reverse: false,
|
|
553
|
+
to: 0,
|
|
554
|
+
times: [],
|
|
555
|
+
values: [],
|
|
556
|
+
})
|
|
557
|
+
this.notify()
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
const { track, elapsed, running, value } = playback.snapshot()
|
|
561
|
+
this.node.update({
|
|
562
|
+
value,
|
|
563
|
+
elapsed,
|
|
564
|
+
running,
|
|
565
|
+
duration: track.duration,
|
|
566
|
+
delay: track.delay,
|
|
567
|
+
repeat: Number.isFinite(track.repeat) ? track.repeat : -1,
|
|
568
|
+
reverse: track.reverse,
|
|
569
|
+
to: track.to,
|
|
570
|
+
times: track.times,
|
|
571
|
+
values: track.values,
|
|
572
|
+
})
|
|
573
|
+
this.notify()
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
root.listeners.add(update)
|
|
577
|
+
this.releaseSource = () => {
|
|
578
|
+
root.listeners.delete(update)
|
|
579
|
+
}
|
|
580
|
+
this.releaseReduced = onReducedMotion((reduced) => {
|
|
581
|
+
this.node.reduced(instant || reduced)
|
|
582
|
+
this.notify()
|
|
583
|
+
})
|
|
584
|
+
Application.on(Application.suspendEvent, this.suspend)
|
|
585
|
+
Application.on(Application.resumeEvent, this.resume)
|
|
586
|
+
update()
|
|
587
|
+
} catch (error) {
|
|
588
|
+
this.dispose()
|
|
589
|
+
throw error
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
override raw(): number {
|
|
593
|
+
return this.node?.position() ?? super.raw()
|
|
594
|
+
}
|
|
595
|
+
override get running(): boolean {
|
|
596
|
+
return !this.ended && (this.node?.moving() ?? false)
|
|
597
|
+
}
|
|
598
|
+
get moving(): boolean {
|
|
599
|
+
return this.running
|
|
600
|
+
}
|
|
601
|
+
get disposed(): boolean {
|
|
602
|
+
return this.ended
|
|
603
|
+
}
|
|
604
|
+
override get paused(): boolean {
|
|
605
|
+
return this.held
|
|
606
|
+
}
|
|
607
|
+
pause() {
|
|
608
|
+
if (!this.ended) {
|
|
609
|
+
this.held = true
|
|
610
|
+
this.updatePause()
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
play() {
|
|
614
|
+
if (!this.ended) {
|
|
615
|
+
this.held = false
|
|
616
|
+
this.updatePause()
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
private updatePause() {
|
|
620
|
+
this.node.paused(this.held || this.background)
|
|
621
|
+
this.notify()
|
|
622
|
+
}
|
|
623
|
+
getVelocity(): number {
|
|
624
|
+
return this.ended ? 0 : this.node.velocity()
|
|
625
|
+
}
|
|
626
|
+
override plan(): Plan {
|
|
627
|
+
throw new TypeError(
|
|
628
|
+
'A live spring cannot be sampled as a fixed track. Bind it to transforms or opacity.',
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
notify() {
|
|
632
|
+
this.observers.changed(this.running)
|
|
633
|
+
for (const listener of [...this.listeners]) listener()
|
|
634
|
+
}
|
|
635
|
+
dispose() {
|
|
636
|
+
if (this.ended) return
|
|
637
|
+
this.ended = true
|
|
638
|
+
this.releaseSource()
|
|
639
|
+
this.releaseReduced()
|
|
640
|
+
Application.off(Application.suspendEvent, this.suspend)
|
|
641
|
+
Application.off(Application.resumeEvent, this.resume)
|
|
642
|
+
for (const binding of [...this.bindings]) release(binding)
|
|
643
|
+
this.node.dispose()
|
|
644
|
+
this.observers.clear()
|
|
645
|
+
for (const listener of [...this.listeners]) listener()
|
|
646
|
+
this.listeners.clear()
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** Follow changing targets without rebuilding sampled animation tracks. Dispose with the owning scope. */
|
|
651
|
+
export function springValue(
|
|
652
|
+
source: DerivedValue,
|
|
653
|
+
options: SpringValueOptions = {},
|
|
654
|
+
): SpringValue {
|
|
655
|
+
if (!(source instanceof Derived) || source.color)
|
|
656
|
+
throw new TypeError('springValue() needs a numeric Jolt motion value.')
|
|
657
|
+
if (!options || typeof options !== 'object')
|
|
658
|
+
throw new TypeError('springValue() options must be an object.')
|
|
659
|
+
return new SpringRoot(source, options) as unknown as SpringValue
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** Internal boundary notification for values driven directly by native input. */
|
|
663
|
+
export function liveValueInput(value: DerivedValue) {
|
|
664
|
+
if (!(value instanceof Derived) || !(value.root instanceof SpringRoot))
|
|
665
|
+
throw new TypeError('Native input requires a live value.')
|
|
666
|
+
const root = value.root
|
|
667
|
+
return { node: root.node, changed: () => root.notify() }
|
|
668
|
+
}
|
|
669
|
+
|
|
438
670
|
function layerName(name: string): string {
|
|
439
671
|
if (typeof name !== 'string' || !name.length)
|
|
440
672
|
throw new TypeError('A layer is named by a nonempty string.')
|
|
@@ -462,7 +694,7 @@ export function motionValue(initial = 0): MotionValue {
|
|
|
462
694
|
}
|
|
463
695
|
|
|
464
696
|
export function isMotionValue(target: unknown): target is MotionValue {
|
|
465
|
-
return target instanceof Root
|
|
697
|
+
return target instanceof Root && !(target instanceof SpringRoot)
|
|
466
698
|
}
|
|
467
699
|
|
|
468
700
|
export function asRoot(value: MotionValue): Root {
|
|
@@ -490,6 +722,13 @@ export function bind(target: Target | readonly Target[], bindings: Bindings): ()
|
|
|
490
722
|
key === 'scale' ? ['scaleX', 'scaleY'] : isProperty(key) ? [key] : []
|
|
491
723
|
if (!properties.length) throw new Error(`Can't bind "${key}".`)
|
|
492
724
|
for (const property of properties) {
|
|
725
|
+
if (source.root instanceof SpringRoot) {
|
|
726
|
+
if (source.root.disposed) throw new Error('Cannot bind a disposed spring value.')
|
|
727
|
+
if (isColorProperty(property) || isDecorationProperty(property))
|
|
728
|
+
throw new TypeError(
|
|
729
|
+
'Live spring bindings currently support numeric transforms and opacity.',
|
|
730
|
+
)
|
|
731
|
+
}
|
|
493
732
|
if (isColorProperty(property) !== source.color)
|
|
494
733
|
throw new TypeError(
|
|
495
734
|
source.color
|
|
@@ -504,9 +743,14 @@ export function bind(target: Target | readonly Target[], bindings: Bindings): ()
|
|
|
504
743
|
if (isColorProperty(property)) validateColorTarget(view, property)
|
|
505
744
|
else if (isDecorationProperty(property)) validateDecorationTarget(view, property)
|
|
506
745
|
|
|
507
|
-
const made
|
|
508
|
-
|
|
509
|
-
|
|
746
|
+
const made: (() => void)[] = []
|
|
747
|
+
try {
|
|
748
|
+
for (const view of views)
|
|
749
|
+
for (const [property, source] of entries) made.push(attach(view, property, source))
|
|
750
|
+
} catch (error) {
|
|
751
|
+
for (const cleanup of made.reverse()) cleanup()
|
|
752
|
+
throw error
|
|
753
|
+
}
|
|
510
754
|
return () => made.forEach((release) => release())
|
|
511
755
|
}
|
|
512
756
|
|
|
@@ -514,6 +758,8 @@ function attach(view: View, property: Property, source: Derived) {
|
|
|
514
758
|
const existing = bound.get(view)?.get(property)
|
|
515
759
|
if (existing) release(existing)
|
|
516
760
|
const root = source.root
|
|
761
|
+
if (root instanceof SpringRoot)
|
|
762
|
+
return attachSpring(view, property as NumericProperty, source, root)
|
|
517
763
|
const binding: Binding = {
|
|
518
764
|
view,
|
|
519
765
|
property,
|
|
@@ -551,7 +797,12 @@ function attach(view: View, property: Property, source: Derived) {
|
|
|
551
797
|
properties.set(property, binding)
|
|
552
798
|
root.bindings.add(binding)
|
|
553
799
|
view.on('loaded', binding.sync)
|
|
554
|
-
|
|
800
|
+
try {
|
|
801
|
+
binding.sync()
|
|
802
|
+
} catch (error) {
|
|
803
|
+
release(binding)
|
|
804
|
+
throw error
|
|
805
|
+
}
|
|
555
806
|
return () => {
|
|
556
807
|
if (root.bindings.has(binding)) {
|
|
557
808
|
release(binding)
|
|
@@ -560,6 +811,61 @@ function attach(view: View, property: Property, source: Derived) {
|
|
|
560
811
|
}
|
|
561
812
|
}
|
|
562
813
|
|
|
814
|
+
function attachSpring(
|
|
815
|
+
view: View,
|
|
816
|
+
property: NumericProperty,
|
|
817
|
+
source: Derived,
|
|
818
|
+
root: SpringRoot,
|
|
819
|
+
) {
|
|
820
|
+
let native: LiveBinding | undefined
|
|
821
|
+
let releaseMix: (() => void) | undefined
|
|
822
|
+
const detach = () => {
|
|
823
|
+
if (!native) return
|
|
824
|
+
native.cancel()
|
|
825
|
+
releaseMix?.()
|
|
826
|
+
releaseMix = undefined
|
|
827
|
+
native = undefined
|
|
828
|
+
syncLiveSprings(view)
|
|
829
|
+
}
|
|
830
|
+
const binding: Binding = {
|
|
831
|
+
view,
|
|
832
|
+
property,
|
|
833
|
+
root,
|
|
834
|
+
read: source.read,
|
|
835
|
+
sync() {
|
|
836
|
+
if (!view.isLoaded || !view.nativeViewProtected || root.disposed) return
|
|
837
|
+
detach()
|
|
838
|
+
stopBase(view, [property])
|
|
839
|
+
native = root.node.bind(view, property, source.mappings)
|
|
840
|
+
const handle = native
|
|
841
|
+
releaseMix = mixFor(view, property).attachLive({
|
|
842
|
+
read: () => handle.value(),
|
|
843
|
+
layers: (layers) => handle.layers(layers),
|
|
844
|
+
})
|
|
845
|
+
syncLiveSprings(view)
|
|
846
|
+
},
|
|
847
|
+
cleanup() {
|
|
848
|
+
view.off('unloaded', detach)
|
|
849
|
+
detach()
|
|
850
|
+
},
|
|
851
|
+
}
|
|
852
|
+
let properties = bound.get(view)
|
|
853
|
+
if (!properties) bound.set(view, (properties = new Map()))
|
|
854
|
+
properties.set(property, binding)
|
|
855
|
+
root.bindings.add(binding)
|
|
856
|
+
view.on('loaded', binding.sync)
|
|
857
|
+
view.on('unloaded', detach)
|
|
858
|
+
try {
|
|
859
|
+
binding.sync()
|
|
860
|
+
} catch (error) {
|
|
861
|
+
release(binding)
|
|
862
|
+
throw error
|
|
863
|
+
}
|
|
864
|
+
return () => {
|
|
865
|
+
if (root.bindings.has(binding)) release(binding)
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
563
869
|
/** A read-only value on an existing finite animation's clock. No second animation or timer. */
|
|
564
870
|
export function playbackProgress(animation: Animation): DerivedValue {
|
|
565
871
|
const source = playbackSource(animation)
|
|
@@ -714,6 +1020,7 @@ function release(binding: Binding) {
|
|
|
714
1020
|
binding.view.off('loaded', binding.sync)
|
|
715
1021
|
const properties = bound.get(binding.view)
|
|
716
1022
|
if (properties?.get(binding.property) === binding) properties.delete(binding.property)
|
|
1023
|
+
binding.cleanup?.()
|
|
717
1024
|
}
|
|
718
1025
|
|
|
719
1026
|
/** A regular animation on a bound property takes it over; the value keeps its other bindings. */
|
|
@@ -775,7 +1082,7 @@ function mapping(
|
|
|
775
1082
|
if (mixers) return mixers[i]!(Math.min(1, Math.max(0, t)))
|
|
776
1083
|
return numbers[i]! + (numbers[i + 1]! - numbers[i]!) * t
|
|
777
1084
|
}
|
|
778
|
-
return { read, color }
|
|
1085
|
+
return { read, color, input, numbers }
|
|
779
1086
|
}
|
|
780
1087
|
|
|
781
1088
|
function springOf(name: SpringName): Transition {
|
package/src/value-observers.ts
CHANGED
|
@@ -81,10 +81,13 @@ export class ValueObservers {
|
|
|
81
81
|
private readonly observers = new Set<Observer>()
|
|
82
82
|
private running = false
|
|
83
83
|
|
|
84
|
-
constructor(
|
|
84
|
+
constructor(
|
|
85
|
+
private readonly read: (time: number) => number,
|
|
86
|
+
private readonly isRunning?: () => boolean,
|
|
87
|
+
) {}
|
|
85
88
|
|
|
86
89
|
get active() {
|
|
87
|
-
return this.running && this.observers.size > 0
|
|
90
|
+
return (this.isRunning?.() ?? this.running) && this.observers.size > 0
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
subscribe(
|
|
@@ -117,6 +120,13 @@ export class ValueObservers {
|
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
clear() {
|
|
124
|
+
this.observers.clear()
|
|
125
|
+
pending.delete(this)
|
|
126
|
+
this.running = false
|
|
127
|
+
schedule()
|
|
128
|
+
}
|
|
129
|
+
|
|
120
130
|
snapshot(time: number): () => void {
|
|
121
131
|
const value = this.read(time)
|
|
122
132
|
const deliveries = [...this.observers].flatMap((observer) => {
|