@rafaelaugusto/jolt 0.1.0-beta.15 → 0.1.0-beta.17
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 +9 -2
- package/dist/bottom-sheet/geometry.d.ts +18 -0
- package/dist/bottom-sheet/geometry.d.ts.map +1 -0
- package/dist/bottom-sheet/geometry.js +51 -0
- package/dist/bottom-sheet/geometry.js.map +1 -0
- package/dist/bottom-sheet/index.d.ts +56 -0
- package/dist/bottom-sheet/index.d.ts.map +1 -0
- package/dist/bottom-sheet/index.js +462 -0
- package/dist/bottom-sheet/index.js.map +1 -0
- package/dist/bottom-sheet/native.d.ts +11 -0
- package/dist/bottom-sheet/native.d.ts.map +1 -0
- package/dist/bottom-sheet/native.js +173 -0
- package/dist/bottom-sheet/native.js.map +1 -0
- 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 +28 -0
- package/dist/gesture-native.d.ts.map +1 -0
- package/dist/gesture-native.js +61 -0
- package/dist/gesture-native.js.map +1 -0
- package/dist/gestures/index.d.ts +69 -0
- package/dist/gestures/index.d.ts.map +1 -0
- package/dist/gestures/index.js +262 -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/native/index.d.ts +2 -0
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +1 -0
- package/dist/native/index.js.map +1 -1
- package/dist/native-property.d.ts +2 -0
- package/dist/native-property.d.ts.map +1 -1
- package/dist/native-property.js +12 -4
- package/dist/native-property.js.map +1 -1
- package/dist/reorder/handle.d.ts +6 -0
- package/dist/reorder/handle.d.ts.map +1 -0
- package/dist/reorder/handle.js +10 -0
- package/dist/reorder/handle.js.map +1 -0
- package/dist/reorder/index.d.ts +10 -0
- package/dist/reorder/index.d.ts.map +1 -0
- package/dist/reorder/index.js +233 -0
- package/dist/reorder/index.js.map +1 -0
- package/dist/reorder/list-view.d.ts +5 -0
- package/dist/reorder/list-view.d.ts.map +1 -0
- package/dist/reorder/list-view.js +238 -0
- package/dist/reorder/list-view.js.map +1 -0
- package/dist/reorder/model.d.ts +6 -0
- package/dist/reorder/model.d.ts.map +1 -0
- package/dist/reorder/model.js +30 -0
- package/dist/reorder/model.js.map +1 -0
- package/dist/reorder/surface.d.ts +6 -0
- package/dist/reorder/surface.d.ts.map +1 -0
- package/dist/reorder/surface.js +10 -0
- package/dist/reorder/surface.js.map +1 -0
- package/dist/reorder/types.d.ts +92 -0
- package/dist/reorder/types.d.ts.map +1 -0
- package/dist/reorder/types.js +2 -0
- package/dist/reorder/types.js.map +1 -0
- package/dist/scene-shader.d.ts +18 -0
- package/dist/scene-shader.d.ts.map +1 -0
- package/dist/scene-shader.js +76 -0
- package/dist/scene-shader.js.map +1 -0
- 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 +13 -1
- package/platforms/android/include.gradle +9 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/GestureSession.java +198 -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/ReorderHandle.java +25 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSession.java +213 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSurface.java +27 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/SpringState.java +39 -0
- package/platforms/android/proguard-rules.pro +5 -0
- package/platforms/ios/src/JoltGestureSession.h +18 -0
- package/platforms/ios/src/JoltGestureSession.m +222 -0
- package/platforms/ios/src/JoltLiveSpring.h +28 -0
- package/platforms/ios/src/JoltLiveSpring.m +236 -0
- package/platforms/ios/src/JoltReorderSession.h +15 -0
- package/platforms/ios/src/JoltReorderSession.m +205 -0
- package/platforms/ios/src/JoltSpringState.h +35 -0
- package/platforms/ios/src/module.modulemap +3 -0
- package/src/android-plugin.d.ts +93 -0
- package/src/bottom-sheet/geometry.ts +84 -0
- package/src/bottom-sheet/index.ts +536 -0
- package/src/bottom-sheet/native.ts +214 -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 +139 -0
- package/src/gestures/index.ts +365 -0
- package/src/index.ts +3 -1
- package/src/ios-plugin.d.ts +90 -0
- package/src/live-spring-driver.ts +181 -0
- package/src/mixer.ts +43 -1
- package/src/motion-value.ts +318 -11
- package/src/native/index.ts +6 -0
- package/src/native-property.ts +17 -4
- package/src/reorder/handle.ts +9 -0
- package/src/reorder/index.ts +243 -0
- package/src/reorder/list-view.ts +262 -0
- package/src/reorder/model.ts +47 -0
- package/src/reorder/surface.ts +10 -0
- package/src/reorder/types.ts +87 -0
- package/src/scene-shader.ts +139 -0
- package/src/value-observers.ts +12 -2
package/src/mixer.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { View } from '@nativescript/core'
|
|
|
2
2
|
import { getDriver } from './driver/index.js'
|
|
3
3
|
import { readValue, writeValue, type Property } from './properties.js'
|
|
4
4
|
import type { Track } from './track.js'
|
|
5
|
+
import type { LiveInput } from './live-spring-driver.js'
|
|
5
6
|
|
|
6
7
|
/** The channel of regular animations; layers are named next to it. */
|
|
7
8
|
export const BASE = ''
|
|
@@ -67,6 +68,7 @@ export function mixFor(view: View, property: Property): Mix {
|
|
|
67
68
|
|
|
68
69
|
/** One property of one view: a base and named layers, played natively as one composite track. */
|
|
69
70
|
export class Mix {
|
|
71
|
+
private live: { read(): number; layers(inputs: readonly LiveInput[]): void } | undefined
|
|
70
72
|
private readonly rests = new Map<string, number>()
|
|
71
73
|
private readonly channels = new Map<string, Channel>()
|
|
72
74
|
private native: unknown
|
|
@@ -80,12 +82,27 @@ export class Mix {
|
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
rest(key: string): number {
|
|
85
|
+
if (key === BASE && this.live) return this.live.read()
|
|
83
86
|
return (
|
|
84
87
|
this.rests.get(key) ??
|
|
85
88
|
(key === BASE ? readValue(this.view, this.property) : identity(this.property))
|
|
86
89
|
)
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
attachLive(base: {
|
|
93
|
+
read(): number
|
|
94
|
+
layers(inputs: readonly LiveInput[]): void
|
|
95
|
+
}): () => void {
|
|
96
|
+
this.live = base
|
|
97
|
+
this.compose()
|
|
98
|
+
return () => {
|
|
99
|
+
if (this.live !== base) return
|
|
100
|
+
this.rests.set(BASE, base.read())
|
|
101
|
+
this.live = undefined
|
|
102
|
+
this.compose()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
setRest(key: string, value: number) {
|
|
90
107
|
this.rests.set(key, value)
|
|
91
108
|
this.compose()
|
|
@@ -192,6 +209,31 @@ export class Mix {
|
|
|
192
209
|
const previous = this.native
|
|
193
210
|
this.native = undefined
|
|
194
211
|
|
|
212
|
+
if (this.live) {
|
|
213
|
+
if (previous !== undefined) driver.stop(this.view, previous)
|
|
214
|
+
const layers: LiveInput[] = []
|
|
215
|
+
for (const key of new Set([...this.rests.keys(), ...this.channels.keys()])) {
|
|
216
|
+
if (key === BASE) continue
|
|
217
|
+
const channel = this.channels.get(key)
|
|
218
|
+
const value = this.part(key, now, now)
|
|
219
|
+
const track = channel?.track
|
|
220
|
+
layers.push({
|
|
221
|
+
value,
|
|
222
|
+
running: !!channel && !channel.paused,
|
|
223
|
+
elapsed: this.elapsed(key, now),
|
|
224
|
+
duration: track?.duration ?? 0,
|
|
225
|
+
delay: track?.delay ?? 0,
|
|
226
|
+
repeat: track ? (Number.isFinite(track.repeat) ? track.repeat : -1) : 0,
|
|
227
|
+
reverse: track?.reverse ?? false,
|
|
228
|
+
to: track?.to ?? value,
|
|
229
|
+
times: track?.times ?? [],
|
|
230
|
+
values: track?.values ?? [],
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
this.live.layers(layers)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
195
237
|
let finite = 0
|
|
196
238
|
const periods: number[] = []
|
|
197
239
|
for (const [key, channel] of this.channels) {
|
|
@@ -245,7 +287,7 @@ export class Mix {
|
|
|
245
287
|
}
|
|
246
288
|
|
|
247
289
|
private dissolve() {
|
|
248
|
-
if (this.channels.size) return
|
|
290
|
+
if (this.live || this.channels.size) return
|
|
249
291
|
for (const [key, value] of this.rests)
|
|
250
292
|
if (key !== BASE && value !== identity(this.property)) return
|
|
251
293
|
const properties = mixes.get(this.view)
|
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/native/index.ts
CHANGED
|
@@ -22,3 +22,9 @@ export {
|
|
|
22
22
|
sceneKitMaterialDiffuse,
|
|
23
23
|
sceneKitMaterialEmission,
|
|
24
24
|
} from '../scene-appearance.js'
|
|
25
|
+
export { sceneKitEnvironmentIntensity, sceneKitUniform } from '../scene-shader.js'
|
|
26
|
+
export type {
|
|
27
|
+
SceneUniformType,
|
|
28
|
+
SceneUniformValue,
|
|
29
|
+
SceneUniformOptions,
|
|
30
|
+
} from '../scene-shader.js'
|
package/src/native-property.ts
CHANGED
|
@@ -56,10 +56,16 @@ const owners = new WeakMap<object, Map<string, Owner>>()
|
|
|
56
56
|
export function registerNativeProperty<T>(
|
|
57
57
|
definition: NativePropertyDefinition<T>,
|
|
58
58
|
): (target: object) => NativeProperty<T> {
|
|
59
|
-
|
|
60
|
-
throw new TypeError('Native property names must be namespaced: plugin/property.')
|
|
59
|
+
validateDefinition(definition)
|
|
61
60
|
if (definitions.has(definition.name))
|
|
62
61
|
throw new Error(`Native property "${definition.name}" is already registered.`)
|
|
62
|
+
definitions.add(definition.name)
|
|
63
|
+
return (target) => new PropertyTarget(target, definition)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validateDefinition<T>(definition: NativePropertyDefinition<T>) {
|
|
67
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(definition.name))
|
|
68
|
+
throw new TypeError('Native property names must be namespaced: plugin/property.')
|
|
63
69
|
if (
|
|
64
70
|
!definition.units.length ||
|
|
65
71
|
definition.units.some((unit) => typeof unit !== 'string' || !unit)
|
|
@@ -76,8 +82,15 @@ export function registerNativeProperty<T>(
|
|
|
76
82
|
typeof definition.decode !== 'function'
|
|
77
83
|
)
|
|
78
84
|
throw new TypeError('A native property needs create(), encode() and decode().')
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Internal factory for per-target schemas; identity still shares the registry ownership map. */
|
|
88
|
+
export function nativePropertyTarget<T>(
|
|
89
|
+
target: object,
|
|
90
|
+
definition: NativePropertyDefinition<T>,
|
|
91
|
+
): NativeProperty<T> {
|
|
92
|
+
validateDefinition(definition)
|
|
93
|
+
return new PropertyTarget(target, definition)
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
class PropertyTarget<T> implements NativeProperty<T> {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Button, isIOS } from '@nativescript/core'
|
|
2
|
+
|
|
3
|
+
/** Focusable native handle: Alt/Option + ↑/↓ moves its current item. */
|
|
4
|
+
export class ReorderHandle extends Button {
|
|
5
|
+
override createNativeView(): ReturnType<Button['createNativeView']> {
|
|
6
|
+
if (isIOS) return JoltReorderHandle.new()
|
|
7
|
+
return new org.rafaelaugusto.jolt.ReorderHandle(this._context)
|
|
8
|
+
}
|
|
9
|
+
}
|