@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.
Files changed (132) hide show
  1. package/README.md +9 -2
  2. package/dist/bottom-sheet/geometry.d.ts +18 -0
  3. package/dist/bottom-sheet/geometry.d.ts.map +1 -0
  4. package/dist/bottom-sheet/geometry.js +51 -0
  5. package/dist/bottom-sheet/geometry.js.map +1 -0
  6. package/dist/bottom-sheet/index.d.ts +56 -0
  7. package/dist/bottom-sheet/index.d.ts.map +1 -0
  8. package/dist/bottom-sheet/index.js +462 -0
  9. package/dist/bottom-sheet/index.js.map +1 -0
  10. package/dist/bottom-sheet/native.d.ts +11 -0
  11. package/dist/bottom-sheet/native.d.ts.map +1 -0
  12. package/dist/bottom-sheet/native.js +173 -0
  13. package/dist/bottom-sheet/native.js.map +1 -0
  14. package/dist/drag.js +1 -1
  15. package/dist/drag.js.map +1 -1
  16. package/dist/driver/android.d.ts.map +1 -1
  17. package/dist/driver/android.js +2 -0
  18. package/dist/driver/android.js.map +1 -1
  19. package/dist/driver/ios-scroll-delegate.d.ts +4 -0
  20. package/dist/driver/ios-scroll-delegate.d.ts.map +1 -0
  21. package/dist/driver/ios-scroll-delegate.js +26 -0
  22. package/dist/driver/ios-scroll-delegate.js.map +1 -0
  23. package/dist/driver/ios.d.ts.map +1 -1
  24. package/dist/driver/ios.js +2 -0
  25. package/dist/driver/ios.js.map +1 -1
  26. package/dist/gesture-native.d.ts +28 -0
  27. package/dist/gesture-native.d.ts.map +1 -0
  28. package/dist/gesture-native.js +61 -0
  29. package/dist/gesture-native.js.map +1 -0
  30. package/dist/gestures/index.d.ts +69 -0
  31. package/dist/gestures/index.d.ts.map +1 -0
  32. package/dist/gestures/index.js +262 -0
  33. package/dist/gestures/index.js.map +1 -0
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/live-spring-driver.d.ts +52 -0
  39. package/dist/live-spring-driver.d.ts.map +1 -0
  40. package/dist/live-spring-driver.js +104 -0
  41. package/dist/live-spring-driver.js.map +1 -0
  42. package/dist/mixer.d.ts +6 -0
  43. package/dist/mixer.d.ts.map +1 -1
  44. package/dist/mixer.js +41 -1
  45. package/dist/mixer.js.map +1 -1
  46. package/dist/motion-value.d.ts +34 -2
  47. package/dist/motion-value.d.ts.map +1 -1
  48. package/dist/motion-value.js +277 -10
  49. package/dist/motion-value.js.map +1 -1
  50. package/dist/native/index.d.ts +2 -0
  51. package/dist/native/index.d.ts.map +1 -1
  52. package/dist/native/index.js +1 -0
  53. package/dist/native/index.js.map +1 -1
  54. package/dist/native-property.d.ts +2 -0
  55. package/dist/native-property.d.ts.map +1 -1
  56. package/dist/native-property.js +12 -4
  57. package/dist/native-property.js.map +1 -1
  58. package/dist/reorder/handle.d.ts +6 -0
  59. package/dist/reorder/handle.d.ts.map +1 -0
  60. package/dist/reorder/handle.js +10 -0
  61. package/dist/reorder/handle.js.map +1 -0
  62. package/dist/reorder/index.d.ts +10 -0
  63. package/dist/reorder/index.d.ts.map +1 -0
  64. package/dist/reorder/index.js +233 -0
  65. package/dist/reorder/index.js.map +1 -0
  66. package/dist/reorder/list-view.d.ts +5 -0
  67. package/dist/reorder/list-view.d.ts.map +1 -0
  68. package/dist/reorder/list-view.js +238 -0
  69. package/dist/reorder/list-view.js.map +1 -0
  70. package/dist/reorder/model.d.ts +6 -0
  71. package/dist/reorder/model.d.ts.map +1 -0
  72. package/dist/reorder/model.js +30 -0
  73. package/dist/reorder/model.js.map +1 -0
  74. package/dist/reorder/surface.d.ts +6 -0
  75. package/dist/reorder/surface.d.ts.map +1 -0
  76. package/dist/reorder/surface.js +10 -0
  77. package/dist/reorder/surface.js.map +1 -0
  78. package/dist/reorder/types.d.ts +92 -0
  79. package/dist/reorder/types.d.ts.map +1 -0
  80. package/dist/reorder/types.js +2 -0
  81. package/dist/reorder/types.js.map +1 -0
  82. package/dist/scene-shader.d.ts +18 -0
  83. package/dist/scene-shader.d.ts.map +1 -0
  84. package/dist/scene-shader.js +76 -0
  85. package/dist/scene-shader.js.map +1 -0
  86. package/dist/value-observers.d.ts +3 -1
  87. package/dist/value-observers.d.ts.map +1 -1
  88. package/dist/value-observers.js +10 -2
  89. package/dist/value-observers.js.map +1 -1
  90. package/package.json +13 -1
  91. package/platforms/android/include.gradle +9 -0
  92. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSession.java +198 -0
  93. package/platforms/android/java/org/rafaelaugusto/jolt/GestureSurface.java +53 -0
  94. package/platforms/android/java/org/rafaelaugusto/jolt/LiveSpring.java +270 -0
  95. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderHandle.java +25 -0
  96. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSession.java +213 -0
  97. package/platforms/android/java/org/rafaelaugusto/jolt/ReorderSurface.java +27 -0
  98. package/platforms/android/java/org/rafaelaugusto/jolt/SpringState.java +39 -0
  99. package/platforms/android/proguard-rules.pro +5 -0
  100. package/platforms/ios/src/JoltGestureSession.h +18 -0
  101. package/platforms/ios/src/JoltGestureSession.m +222 -0
  102. package/platforms/ios/src/JoltLiveSpring.h +28 -0
  103. package/platforms/ios/src/JoltLiveSpring.m +236 -0
  104. package/platforms/ios/src/JoltReorderSession.h +15 -0
  105. package/platforms/ios/src/JoltReorderSession.m +205 -0
  106. package/platforms/ios/src/JoltSpringState.h +35 -0
  107. package/platforms/ios/src/module.modulemap +3 -0
  108. package/src/android-plugin.d.ts +93 -0
  109. package/src/bottom-sheet/geometry.ts +84 -0
  110. package/src/bottom-sheet/index.ts +536 -0
  111. package/src/bottom-sheet/native.ts +214 -0
  112. package/src/drag.ts +1 -1
  113. package/src/driver/android.ts +2 -0
  114. package/src/driver/ios-scroll-delegate.ts +23 -0
  115. package/src/driver/ios.ts +2 -0
  116. package/src/gesture-native.ts +139 -0
  117. package/src/gestures/index.ts +365 -0
  118. package/src/index.ts +3 -1
  119. package/src/ios-plugin.d.ts +90 -0
  120. package/src/live-spring-driver.ts +181 -0
  121. package/src/mixer.ts +43 -1
  122. package/src/motion-value.ts +318 -11
  123. package/src/native/index.ts +6 -0
  124. package/src/native-property.ts +17 -4
  125. package/src/reorder/handle.ts +9 -0
  126. package/src/reorder/index.ts +243 -0
  127. package/src/reorder/list-view.ts +262 -0
  128. package/src/reorder/model.ts +47 -0
  129. package/src/reorder/surface.ts +10 -0
  130. package/src/reorder/types.ts +87 -0
  131. package/src/scene-shader.ts +139 -0
  132. package/src/value-observers.ts +12 -2
@@ -0,0 +1,365 @@
1
+ import { Application, GridLayout, ScrollView, Trace, isAndroid } from '@nativescript/core'
2
+ import {
3
+ createGestureDriver,
4
+ type GestureDriver,
5
+ type GestureSettings,
6
+ } from '../gesture-native.js'
7
+ import {
8
+ motionValue,
9
+ springValue,
10
+ type DerivedValue,
11
+ type SpringValueOptions,
12
+ } from '../motion-value.js'
13
+
14
+ /** A GridLayout that provides native gesture dispatch and nested-scroll consumption. */
15
+ export class GestureSurface extends GridLayout {
16
+ override createNativeView() {
17
+ if (isAndroid) {
18
+ if (typeof org.rafaelaugusto.jolt.GestureSurface !== 'function')
19
+ throw new Error('Rebuild the native app to use GestureSurface.')
20
+ return new org.rafaelaugusto.jolt.GestureSurface(this._context)
21
+ }
22
+ return super.createNativeView()
23
+ }
24
+ }
25
+ export interface GesturePosition {
26
+ x: number
27
+ y: number
28
+ scale: number
29
+ rotate: number
30
+ }
31
+ export interface GestureEvent extends GesturePosition {
32
+ velocityX: number
33
+ velocityY: number
34
+ }
35
+ export interface GestureBounds {
36
+ left?: number
37
+ right?: number
38
+ top?: number
39
+ bottom?: number
40
+ }
41
+ export interface GesturesOptions {
42
+ pan?:
43
+ | boolean
44
+ | {
45
+ axis?: 'x' | 'y' | 'both'
46
+ threshold?: number
47
+ failOffset?: number
48
+ bounds?: GestureBounds
49
+ }
50
+ pinch?: boolean | { min?: number; max?: number }
51
+ rotate?: boolean
52
+ initial?: Partial<GesturePosition>
53
+ spring?: Pick<SpringValueOptions, 'stiffness' | 'damping' | 'mass'>
54
+ /** A descendant, vertical NativeScript ScrollView. Other list plugins are not supported. */
55
+ scrollView?: ScrollView
56
+ /** First contact, before pan recognition; useful when a touch interrupts a spring. */
57
+ onContactStart?: (event: GestureEvent) => void
58
+ /** Last contact released or cancelled, including a touch that never becomes a pan. */
59
+ onContactEnd?: (event: GestureEvent) => void
60
+ onStart?: (event: GestureEvent) => void
61
+ onEnd?: (event: GestureEvent) => void
62
+ onCancel?: (event: GestureEvent) => void
63
+ onTap?: (event: GestureEvent) => void
64
+ onDoubleTap?: (event: GestureEvent) => void
65
+ }
66
+ export interface Gestures {
67
+ readonly x: DerivedValue
68
+ readonly y: DerivedValue
69
+ readonly scale: DerivedValue
70
+ readonly rotate: DerivedValue
71
+ readonly position: GesturePosition
72
+ readonly active: boolean
73
+ readonly paused: boolean
74
+ readonly disposed: boolean
75
+ /** Spring toward a new position, carrying the last contact's velocity. */
76
+ settleTo(position: Partial<GesturePosition>): void
77
+ jumpTo(position: Partial<GesturePosition>): void
78
+ updateBounds(bounds: GestureBounds): void
79
+ cancel(): void
80
+ pause(): void
81
+ play(): void
82
+ dispose(): void
83
+ }
84
+ const owners = new WeakMap<GestureSurface, Gestures>()
85
+ const LIMIT = 1e9
86
+ function bounds(input: GestureBounds = {}) {
87
+ if (!input || typeof input !== 'object')
88
+ throw new TypeError('Gesture bounds must be an object.')
89
+ const result = {
90
+ minX: input.left ?? -LIMIT,
91
+ maxX: input.right ?? LIMIT,
92
+ minY: input.top ?? -LIMIT,
93
+ maxY: input.bottom ?? LIMIT,
94
+ }
95
+ if (
96
+ Object.values(result).some((value) => !Number.isFinite(value)) ||
97
+ result.minX > result.maxX ||
98
+ result.minY > result.maxY
99
+ )
100
+ throw new RangeError('Gesture bounds must be finite and ordered.')
101
+ return result
102
+ }
103
+ function settings(options: GesturesOptions): GestureSettings {
104
+ for (const key of ['pan', 'pinch'] as const)
105
+ if (
106
+ options[key] !== undefined &&
107
+ typeof options[key] !== 'boolean' &&
108
+ (!options[key] || typeof options[key] !== 'object')
109
+ )
110
+ throw new TypeError(`Gesture ${key} must be boolean or an options object.`)
111
+ if (options.rotate !== undefined && typeof options.rotate !== 'boolean')
112
+ throw new TypeError('Gesture rotate must be boolean.')
113
+ for (const key of [
114
+ 'onStart',
115
+ 'onEnd',
116
+ 'onCancel',
117
+ 'onTap',
118
+ 'onDoubleTap',
119
+ 'onContactStart',
120
+ 'onContactEnd',
121
+ ] as const)
122
+ if (options[key] !== undefined && typeof options[key] !== 'function')
123
+ throw new TypeError(`Gesture ${key} must be a function.`)
124
+ const pan = typeof options.pan === 'object' ? options.pan : {}
125
+ const pinch = typeof options.pinch === 'object' ? options.pinch : {}
126
+ const axis = pan.axis ?? 'both'
127
+ if (!['x', 'y', 'both'].includes(axis))
128
+ throw new TypeError('Gesture pan axis must be x, y or both.')
129
+ const result = {
130
+ ...bounds(pan.bounds),
131
+ axis: options.pan ? (axis === 'x' ? 1 : axis === 'y' ? 2 : 3) : 0,
132
+ threshold: pan.threshold ?? 8,
133
+ failOffset: pan.failOffset ?? 12,
134
+ pinch: !!options.pinch,
135
+ rotate: !!options.rotate,
136
+ tap: !!options.onTap,
137
+ doubleTap: !!options.onDoubleTap,
138
+ contactEvents: !!(options.onContactStart || options.onContactEnd),
139
+ minScale: pinch.min ?? 0.25,
140
+ maxScale: pinch.max ?? 4,
141
+ }
142
+ if (
143
+ ![result.threshold, result.failOffset, result.minScale, result.maxScale].every(
144
+ (value) => Number.isFinite(value) && value > 0,
145
+ ) ||
146
+ result.minScale > result.maxScale
147
+ )
148
+ throw new RangeError(
149
+ 'Gesture thresholds and scale limits must be positive and ordered.',
150
+ )
151
+ if (
152
+ options.scrollView &&
153
+ (!(options.scrollView instanceof ScrollView) ||
154
+ options.scrollView.orientation !== 'vertical' ||
155
+ result.axis !== 2)
156
+ )
157
+ throw new TypeError('Scroll handoff needs a vertical ScrollView and a y-axis pan.')
158
+ if (
159
+ options.scrollView &&
160
+ (pan.bounds?.top === undefined || pan.bounds?.bottom === undefined)
161
+ )
162
+ throw new TypeError('Scroll handoff needs explicit top and bottom pan bounds.')
163
+ return result
164
+ }
165
+
166
+ /** Compose native pan/pinch/rotation and exclusive single/double taps on one surface. */
167
+ export function gestures(
168
+ surface: GestureSurface,
169
+ options: GesturesOptions = {},
170
+ ): Gestures {
171
+ if (!(surface instanceof GestureSurface))
172
+ throw new TypeError('gestures() requires a GestureSurface.')
173
+ if (!options || typeof options !== 'object')
174
+ throw new TypeError('Gesture options must be an object.')
175
+ let config = settings(options)
176
+ if (options.scrollView) {
177
+ let ancestor = options.scrollView.parent
178
+ while (ancestor && ancestor !== surface) ancestor = ancestor.parent
179
+ if (ancestor !== surface)
180
+ throw new TypeError('The ScrollView must be inside the GestureSurface.')
181
+ }
182
+ const initial = { x: 0, y: 0, scale: 1, rotate: 0, ...options.initial }
183
+ if (Object.values(initial).some((value) => !Number.isFinite(value)))
184
+ throw new RangeError('Initial gesture values must be finite.')
185
+ if (
186
+ initial.x < config.minX ||
187
+ initial.x > config.maxX ||
188
+ initial.y < config.minY ||
189
+ initial.y > config.maxY ||
190
+ initial.scale < config.minScale ||
191
+ initial.scale > config.maxScale
192
+ )
193
+ throw new RangeError('Initial gesture values must be within their bounds.')
194
+ const keys = ['x', 'y', 'scale', 'rotate'] as const
195
+ const values = keys.map((key) => springValue(motionValue(initial[key]), options.spring))
196
+ let driver: GestureDriver | undefined,
197
+ ended = false,
198
+ held = false,
199
+ suspended = false,
200
+ disposing = false
201
+ const position = (): GesturePosition => ({
202
+ x: values[0]!.get(),
203
+ y: values[1]!.get(),
204
+ scale: values[2]!.get(),
205
+ rotate: values[3]!.get(),
206
+ })
207
+ const callback = (state: number, velocityX: number, velocityY: number) => {
208
+ if (ended) return
209
+ const event = { ...position(), velocityX, velocityY }
210
+ const handler = [
211
+ undefined,
212
+ options.onStart,
213
+ options.onEnd,
214
+ options.onCancel,
215
+ options.onTap,
216
+ options.onDoubleTap,
217
+ options.onContactStart,
218
+ options.onContactEnd,
219
+ ][state]
220
+ try {
221
+ handler?.(event)
222
+ } catch (error) {
223
+ Trace.write(
224
+ `Jolt gesture callback failed: ${String(error)}`,
225
+ Trace.categories.Error,
226
+ Trace.messageType.error,
227
+ )
228
+ }
229
+ }
230
+ const detach = () => {
231
+ const previous = driver
232
+ driver = undefined
233
+ previous?.dispose()
234
+ }
235
+ const refreshEnabled = () =>
236
+ driver?.enabled(
237
+ !held &&
238
+ !suspended &&
239
+ surface.isEnabled !== false &&
240
+ surface.isUserInteractionEnabled !== false,
241
+ )
242
+ const attach = () => {
243
+ if (
244
+ ended ||
245
+ driver ||
246
+ !surface.isLoaded ||
247
+ !surface.nativeViewProtected ||
248
+ (options.scrollView && !options.scrollView.nativeViewProtected)
249
+ )
250
+ return
251
+ try {
252
+ driver = createGestureDriver(surface, options.scrollView, values, config, callback)
253
+ refreshEnabled()
254
+ } catch (error) {
255
+ control.dispose()
256
+ throw error
257
+ }
258
+ }
259
+ const suspend = () => {
260
+ suspended = true
261
+ driver?.enabled(false)
262
+ }
263
+ const resume = () => {
264
+ suspended = false
265
+ refreshEnabled()
266
+ }
267
+ const move = (next: Partial<GesturePosition>, animated: boolean) => {
268
+ if (ended) return
269
+ if (
270
+ !next ||
271
+ typeof next !== 'object' ||
272
+ Object.entries(next).some(
273
+ ([key, value]) =>
274
+ !keys.includes(key as (typeof keys)[number]) || !Number.isFinite(value),
275
+ )
276
+ )
277
+ throw new TypeError(
278
+ 'Gesture positions must contain finite x, y, scale or rotate values.',
279
+ )
280
+ if (!driver)
281
+ throw new Error('Load the GestureSurface before moving its gesture values.')
282
+ const target = { ...position(), ...next }
283
+ driver.settle(
284
+ keys.map((key) => target[key]),
285
+ animated,
286
+ )
287
+ }
288
+ const control: Gestures = {
289
+ x: values[0]!,
290
+ y: values[1]!,
291
+ scale: values[2]!,
292
+ rotate: values[3]!,
293
+ get position() {
294
+ return position()
295
+ },
296
+ get active() {
297
+ return driver?.active ?? false
298
+ },
299
+ get paused() {
300
+ return held
301
+ },
302
+ get disposed() {
303
+ return ended
304
+ },
305
+ settleTo: (next) => move(next, true),
306
+ jumpTo: (next) => move(next, false),
307
+ updateBounds(next) {
308
+ config = {
309
+ ...config,
310
+ ...bounds({
311
+ left: config.minX,
312
+ right: config.maxX,
313
+ top: config.minY,
314
+ bottom: config.maxY,
315
+ ...next,
316
+ }),
317
+ }
318
+ driver?.bounds(config)
319
+ },
320
+ cancel() {
321
+ driver?.cancel()
322
+ },
323
+ pause() {
324
+ held = true
325
+ driver?.enabled(false)
326
+ },
327
+ play() {
328
+ held = false
329
+ refreshEnabled()
330
+ },
331
+ dispose() {
332
+ if (ended || disposing) return
333
+ disposing = true
334
+ detach()
335
+ ended = true
336
+ surface.off('loaded', attach)
337
+ surface.off('unloaded', detach)
338
+ surface.off('isEnabledChange', refreshEnabled)
339
+ surface.off('isUserInteractionEnabledChange', refreshEnabled)
340
+ options.scrollView?.off('loaded', attach)
341
+ options.scrollView?.off('unloaded', detach)
342
+ Application.off(Application.suspendEvent, suspend)
343
+ Application.off(Application.resumeEvent, resume)
344
+ values.forEach((value) => value.dispose())
345
+ if (owners.get(surface) === control) owners.delete(surface)
346
+ },
347
+ }
348
+ owners.get(surface)?.dispose()
349
+ owners.set(surface, control)
350
+ surface.on('loaded', attach)
351
+ surface.on('unloaded', detach)
352
+ surface.on('isEnabledChange', refreshEnabled)
353
+ surface.on('isUserInteractionEnabledChange', refreshEnabled)
354
+ options.scrollView?.on('loaded', attach)
355
+ options.scrollView?.on('unloaded', detach)
356
+ Application.on(Application.suspendEvent, suspend)
357
+ Application.on(Application.resumeEvent, resume)
358
+ try {
359
+ attach()
360
+ } catch (error) {
361
+ control.dispose()
362
+ throw error
363
+ }
364
+ return control
365
+ }
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ export { timeline } from './sequence.js'
2
2
  export type { TimelineAnimation, TimelineStep, PlayableAnimation } from './sequence.js'
3
3
  export { playbackProgress } from './motion-value.js'
4
4
  export { animate, decay, set, stop } from './animate.js'
5
- export { bind, motionValue, scrollValue } from './motion-value.js'
5
+ export { bind, motionValue, scrollValue, springValue } from './motion-value.js'
6
6
  export type {
7
7
  Bindings,
8
8
  DerivedColor,
@@ -10,6 +10,8 @@ export type {
10
10
  MapOptions,
11
11
  MotionValue,
12
12
  ScrollValue,
13
+ SpringValue,
14
+ SpringValueOptions,
13
15
  ValueAnimationOptions,
14
16
  } from './motion-value.js'
15
17
  export type { SubscribeOptions, ValueListener } from './value-observers.js'
@@ -1,3 +1,78 @@
1
+ declare class JoltReorderHandle extends UIButton {
2
+ onMove: ((direction: number) => void) | null
3
+ }
4
+ declare class JoltReorderSession extends NSObject {
5
+ static attachOptionsOnState(
6
+ list: UITableView,
7
+ options: string,
8
+ callback: (state: number, index: number, toIndex: number) => void,
9
+ ): JoltReorderSession
10
+ readonly active: boolean
11
+ registerHandle(view: UIView): void
12
+ setEnabled(enabled: boolean): void
13
+ setReducedMotion(reduced: boolean): void
14
+ cancel(): void
15
+ dispose(): void
16
+ }
17
+ declare class JoltGestureSession extends NSObject {
18
+ static supportsContactEvents(): boolean
19
+ static attachScrollXYScaleRotationOptionsOnState(
20
+ surface: UIView,
21
+ scroll: UIScrollView | null,
22
+ x: JoltLiveSpring,
23
+ y: JoltLiveSpring,
24
+ scale: JoltLiveSpring,
25
+ rotation: JoltLiveSpring,
26
+ options: string,
27
+ onState: (state: number, velocityX: number, velocityY: number) => void,
28
+ ): JoltGestureSession
29
+ readonly active: boolean
30
+ cancel(): void
31
+ refreshScrollDelegate(): void
32
+ setEnabled(value: boolean): void
33
+ settleXYScaleRotationAnimated(
34
+ x: number,
35
+ y: number,
36
+ scale: number,
37
+ rotation: number,
38
+ animated: boolean,
39
+ ): void
40
+ updateLeftRightTopBottom(left: number, right: number, top: number, bottom: number): void
41
+ dispose(): void
42
+ }
43
+ declare class JoltLiveSpring extends NSObject {
44
+ static createStiffnessDampingMassRestDeltaRestSpeedReduced(
45
+ initial: number,
46
+ stiffness: number,
47
+ damping: number,
48
+ mass: number,
49
+ restDelta: number,
50
+ restSpeed: number,
51
+ reduced: boolean,
52
+ ): JoltLiveSpring
53
+ static activeCount(): number
54
+ static nodeCount(): number
55
+ static sync(view: UIView): void
56
+ readonly position: number
57
+ readonly velocity: number
58
+ readonly moving: boolean
59
+ followMappings(source: JoltLiveSpring, mappings: string): void
60
+ updateInput(snapshot: string): void
61
+ setTarget(value: number): void
62
+ setReduced(value: boolean): void
63
+ setPaused(value: boolean): void
64
+ bindPropertyMappings(
65
+ view: UIView,
66
+ property: string,
67
+ mappings: string,
68
+ ): JoltLiveSpringBinding
69
+ dispose(): void
70
+ }
71
+ declare class JoltLiveSpringBinding extends NSObject {
72
+ readonly value: number
73
+ setLayers(inputs: string): void
74
+ cancel(): void
75
+ }
1
76
  declare class JoltInteractiveAnimation extends NSObject {
2
77
  static startViewLayersKeyPathValuesTimesDurationDelayRepeatReverseFinalValueStartedAtCompletion(
3
78
  view: UIView,
@@ -122,3 +197,18 @@ declare class JoltReferenceProperties extends NSObject {
122
197
  static sceneAppearanceSinkCount(): number
123
198
  static sceneAppearanceWriteCount(): number
124
199
  }
200
+
201
+ declare class JoltSceneShaderProperties extends NSObject {
202
+ static environmentError(
203
+ scene: NSObject,
204
+ error: interop.Reference<NSError>,
205
+ ): JoltPropertySink | null
206
+ static uniformNameConfigurationError(
207
+ target: NSObject,
208
+ name: string,
209
+ configuration: string,
210
+ error: interop.Reference<NSError>,
211
+ ): JoltPropertySink | null
212
+ static sinkCount(): number
213
+ static writeCount(): number
214
+ }
@@ -0,0 +1,181 @@
1
+ import { isAndroid, type View } from '@nativescript/core'
2
+ import type { NumericProperty } from './properties.js'
3
+ import { retainInteractiveLayout } from './driver/ios-layout.js'
4
+
5
+ export interface LiveMapping {
6
+ input: readonly number[]
7
+ output: readonly number[]
8
+ clamp: boolean
9
+ }
10
+ export interface LiveSpringPhysics {
11
+ stiffness: number
12
+ damping: number
13
+ mass: number
14
+ restDelta: number
15
+ restSpeed: number
16
+ }
17
+ export interface LiveInput {
18
+ value: number
19
+ running: boolean
20
+ elapsed: number
21
+ duration: number
22
+ delay: number
23
+ repeat: number
24
+ reverse: boolean
25
+ to: number
26
+ times: readonly number[]
27
+ values: readonly number[]
28
+ }
29
+ export interface LiveBinding {
30
+ value(): number
31
+ layers(inputs: readonly LiveInput[]): void
32
+ cancel(): void
33
+ }
34
+ export interface LiveNode {
35
+ position(): number
36
+ velocity(): number
37
+ moving(): boolean
38
+ follow(source: LiveNode, maps: readonly LiveMapping[]): void
39
+ update(input: LiveInput): void
40
+ reduced(value: boolean): void
41
+ paused(value: boolean): void
42
+ bind(view: View, property: NumericProperty, maps: readonly LiveMapping[]): LiveBinding
43
+ dispose(): void
44
+ }
45
+ export interface LiveSpringDriver {
46
+ create(initial: number, physics: LiveSpringPhysics, reduced: boolean): LiveNode
47
+ sync(view: View): void
48
+ }
49
+ let override: LiveSpringDriver | undefined
50
+ export function setLiveSpringDriver(driver: LiveSpringDriver | undefined) {
51
+ override = driver
52
+ }
53
+ export function liveSpringDriver(): LiveSpringDriver {
54
+ if (override) return override
55
+ if (!available())
56
+ throw new Error(
57
+ 'springValue() needs the Jolt native classes. Rebuild the native app after upgrading Jolt.',
58
+ )
59
+ return nativeDriver
60
+ }
61
+ function available() {
62
+ return isAndroid
63
+ ? typeof org !== 'undefined' &&
64
+ typeof org.rafaelaugusto.jolt.LiveSpring === 'function'
65
+ : typeof JoltLiveSpring !== 'undefined'
66
+ }
67
+ export function syncLiveSprings(view: View) {
68
+ if (override) override.sync(view)
69
+ else if (available() && view.nativeViewProtected) nativeDriver.sync(view)
70
+ }
71
+ const handles = new WeakMap<
72
+ LiveNode,
73
+ org.rafaelaugusto.jolt.LiveSpring | JoltLiveSpring
74
+ >()
75
+ /** Internal native input connection; never exposes handles through the public value API. */
76
+ export function nativeLiveNode(node: LiveNode) {
77
+ const handle = handles.get(node)
78
+ if (!handle) throw new Error('A gesture needs a native live value node.')
79
+ return handle
80
+ }
81
+ const nativeDriver: LiveSpringDriver = {
82
+ create(initial, physics, reduced) {
83
+ const { stiffness, damping, mass, restDelta, restSpeed } = physics
84
+ if (isAndroid) {
85
+ const native = new org.rafaelaugusto.jolt.LiveSpring(
86
+ initial,
87
+ stiffness,
88
+ damping,
89
+ mass,
90
+ restDelta,
91
+ restSpeed,
92
+ reduced,
93
+ )
94
+ const node: LiveNode = {
95
+ position: () => native.getPosition(),
96
+ velocity: () => native.getVelocity(),
97
+ moving: () => native.isMoving(),
98
+ follow: (source, maps) =>
99
+ native.follow(
100
+ handles.get(source) as org.rafaelaugusto.jolt.LiveSpring,
101
+ JSON.stringify(maps),
102
+ ),
103
+ update: (input) =>
104
+ input.running
105
+ ? native.updateInput(JSON.stringify(input))
106
+ : native.setTarget(input.value),
107
+ reduced: (value) => native.setReduced(value),
108
+ paused: (value) => native.setPaused(value),
109
+ bind(view, property, maps) {
110
+ const binding = native.bind(
111
+ view.nativeViewProtected,
112
+ property,
113
+ JSON.stringify(maps),
114
+ )
115
+ return {
116
+ value: () => binding.getValue(),
117
+ layers: (inputs) => binding.setLayers(JSON.stringify(inputs)),
118
+ cancel: () => binding.cancel(),
119
+ }
120
+ },
121
+ dispose: () => native.dispose(),
122
+ }
123
+ handles.set(node, native)
124
+ return node
125
+ }
126
+ const native = JoltLiveSpring.createStiffnessDampingMassRestDeltaRestSpeedReduced(
127
+ initial,
128
+ stiffness,
129
+ damping,
130
+ mass,
131
+ restDelta,
132
+ restSpeed,
133
+ reduced,
134
+ )
135
+ const node: LiveNode = {
136
+ position: () => native.position,
137
+ velocity: () => native.velocity,
138
+ moving: () => native.moving,
139
+ follow: (source, maps) =>
140
+ native.followMappings(
141
+ handles.get(source) as JoltLiveSpring,
142
+ JSON.stringify(maps),
143
+ ),
144
+ update: (input) =>
145
+ input.running
146
+ ? native.updateInput(JSON.stringify(input))
147
+ : native.setTarget(input.value),
148
+ reduced: (value) => native.setReduced(value),
149
+ paused: (value) => native.setPaused(value),
150
+ bind(view, property, maps) {
151
+ const releaseLayout = retainInteractiveLayout(view)
152
+ let binding: JoltLiveSpringBinding
153
+ try {
154
+ binding = native.bindPropertyMappings(
155
+ view.nativeViewProtected,
156
+ property,
157
+ JSON.stringify(maps),
158
+ )
159
+ } catch (error) {
160
+ releaseLayout()
161
+ throw error
162
+ }
163
+ return {
164
+ value: () => binding.value,
165
+ layers: (inputs) => binding.setLayers(JSON.stringify(inputs)),
166
+ cancel: () => {
167
+ binding.cancel()
168
+ releaseLayout()
169
+ },
170
+ }
171
+ },
172
+ dispose: () => native.dispose(),
173
+ }
174
+ handles.set(node, native)
175
+ return node
176
+ },
177
+ sync(view) {
178
+ if (isAndroid) org.rafaelaugusto.jolt.LiveSpring.sync(view.nativeViewProtected)
179
+ else JoltLiveSpring.sync(view.nativeViewProtected)
180
+ },
181
+ }