@rafaelaugusto/jolt 0.1.0-beta.10 → 0.1.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -6
- package/dist/animate.d.ts +14 -4
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +50 -34
- package/dist/animate.js.map +1 -1
- package/dist/drag.d.ts +17 -3
- package/dist/drag.d.ts.map +1 -1
- package/dist/drag.js +34 -11
- package/dist/drag.js.map +1 -1
- package/dist/driver/android-gradient.d.ts +5 -0
- package/dist/driver/android-gradient.d.ts.map +1 -0
- package/dist/driver/android-gradient.js +89 -0
- package/dist/driver/android-gradient.js.map +1 -0
- package/dist/driver/ios-gradient.d.ts +5 -0
- package/dist/driver/ios-gradient.d.ts.map +1 -0
- package/dist/driver/ios-gradient.js +109 -0
- package/dist/driver/ios-gradient.js.map +1 -0
- package/dist/easing.d.ts +1 -1
- package/dist/easing.d.ts.map +1 -1
- package/dist/easing.js +11 -0
- package/dist/easing.js.map +1 -1
- package/dist/engine.d.ts +18 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +143 -36
- package/dist/engine.js.map +1 -1
- package/dist/gradient.d.ts +90 -0
- package/dist/gradient.d.ts.map +1 -0
- package/dist/gradient.js +253 -0
- package/dist/gradient.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js +63 -10
- package/dist/keyboard.js.map +1 -1
- package/dist/mixer.d.ts +49 -0
- package/dist/mixer.d.ts.map +1 -0
- package/dist/mixer.js +238 -0
- package/dist/mixer.js.map +1 -0
- package/dist/motion-scope.d.ts +4 -1
- package/dist/motion-scope.d.ts.map +1 -1
- package/dist/motion-scope.js +8 -3
- package/dist/motion-scope.js.map +1 -1
- package/dist/motion-states.d.ts +18 -6
- package/dist/motion-states.d.ts.map +1 -1
- package/dist/motion-states.js +114 -26
- package/dist/motion-states.js.map +1 -1
- package/dist/motion-value.d.ts +115 -0
- package/dist/motion-value.d.ts.map +1 -0
- package/dist/motion-value.js +437 -0
- package/dist/motion-value.js.map +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -2
- package/dist/path.js.map +1 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +31 -12
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +30 -4
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +63 -7
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +28 -2
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +78 -2
- package/dist/vue/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrame.java +53 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFrameFollower.java +57 -0
- package/platforms/android/proguard-rules.pro +2 -0
- package/src/android-plugin.d.ts +56 -0
- package/src/animate.ts +99 -16
- package/src/drag.ts +67 -21
- package/src/driver/android-gradient.ts +109 -0
- package/src/driver/ios-gradient.ts +114 -0
- package/src/easing.ts +14 -0
- package/src/engine.ts +158 -53
- package/src/gradient.ts +378 -0
- package/src/index.ts +34 -3
- package/src/keyboard.ts +83 -18
- package/src/mixer.ts +264 -0
- package/src/motion-scope.ts +14 -6
- package/src/motion-states.ts +145 -41
- package/src/motion-value.ts +563 -0
- package/src/path.ts +2 -1
- package/src/track.ts +37 -12
- package/src/transition.ts +99 -10
- package/src/types.ts +5 -0
- package/src/vue/index.ts +98 -1
package/src/gradient.ts
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { isAndroid, isIOS, type View } from '@nativescript/core'
|
|
2
|
+
import { toView } from './animate.js'
|
|
3
|
+
import { colorMixer, parseColor, type ColorValue } from './color.js'
|
|
4
|
+
import { reducedMotion } from './config.js'
|
|
5
|
+
import { getDriver } from './driver/index.js'
|
|
6
|
+
import { androidGradient } from './driver/android-gradient.js'
|
|
7
|
+
import { iosGradient } from './driver/ios-gradient.js'
|
|
8
|
+
import type { Animation } from './engine.js'
|
|
9
|
+
import { createTrack, type Track } from './track.js'
|
|
10
|
+
import {
|
|
11
|
+
resolveTransition,
|
|
12
|
+
springs,
|
|
13
|
+
type SpringName,
|
|
14
|
+
type Transition,
|
|
15
|
+
} from './transition.js'
|
|
16
|
+
import type { Target } from './types.js'
|
|
17
|
+
|
|
18
|
+
export type GradientType = 'linear' | 'radial' | 'conic'
|
|
19
|
+
|
|
20
|
+
export interface GradientValues {
|
|
21
|
+
/** Two to eight colors. Animating keeps the count. */
|
|
22
|
+
colors?: readonly ColorValue[]
|
|
23
|
+
/** Where each color sits, from 0 to 1. Evenly spaced by default. */
|
|
24
|
+
stops?: readonly number[]
|
|
25
|
+
/** Degrees, like CSS: `linear` points this way (180 runs top to bottom), `conic` starts here (0 is up). */
|
|
26
|
+
angle?: number
|
|
27
|
+
/** `radial` and `conic`: the center, in fractions of the view's width and height. */
|
|
28
|
+
center?: { readonly x: number; readonly y: number }
|
|
29
|
+
/** `radial`: the radius, in fractions of the view's shorter side. */
|
|
30
|
+
radius?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GradientOptions extends GradientValues {
|
|
34
|
+
type: GradientType
|
|
35
|
+
colors: readonly ColorValue[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Gradient {
|
|
39
|
+
readonly type: GradientType
|
|
40
|
+
/** Animates to new values from the ones on screen. Colors and stops keep their count. */
|
|
41
|
+
animate(values: GradientValues, transition?: Transition | SpringName): Animation
|
|
42
|
+
/** Applies values at once, stopping the animation in progress. */
|
|
43
|
+
set(values: GradientValues): void
|
|
44
|
+
/** Stops the animation in progress where it is. */
|
|
45
|
+
stop(): void
|
|
46
|
+
/** Takes the gradient off the view. */
|
|
47
|
+
remove(): void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Everything the platform needs to paint one frame. */
|
|
51
|
+
export interface GradientFrame {
|
|
52
|
+
colors: number[]
|
|
53
|
+
stops: number[]
|
|
54
|
+
angle: number
|
|
55
|
+
x: number
|
|
56
|
+
y: number
|
|
57
|
+
radius: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface GradientTiming {
|
|
61
|
+
duration: number
|
|
62
|
+
delay: number
|
|
63
|
+
repeat: number
|
|
64
|
+
reverse: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface GradientPainter {
|
|
68
|
+
paint(frame: GradientFrame): void
|
|
69
|
+
/** Plays sampled frames natively; `onEnd(false)` when cancelled. */
|
|
70
|
+
play(
|
|
71
|
+
frames: readonly GradientFrame[],
|
|
72
|
+
times: readonly number[],
|
|
73
|
+
timing: GradientTiming,
|
|
74
|
+
onEnd: (finished: boolean) => void,
|
|
75
|
+
): { cancel(): void }
|
|
76
|
+
remove(): void
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type PainterFactory = (view: View, type: GradientType) => GradientPainter | undefined
|
|
80
|
+
|
|
81
|
+
let factory: PainterFactory | undefined
|
|
82
|
+
|
|
83
|
+
/** Test seam, like `setDriver`. */
|
|
84
|
+
export function setGradientPainter(next: PainterFactory | undefined) {
|
|
85
|
+
factory = next
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function platformPainter(view: View, type: GradientType) {
|
|
89
|
+
if (isIOS) return iosGradient(view, type)
|
|
90
|
+
if (isAndroid) return androidGradient(view, type)
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const types: readonly GradientType[] = ['linear', 'radial', 'conic']
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Paints a native gradient behind a layout's children and animates it.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const glow = gradient(halo, { type: 'radial', colors: ['#FF0080', '#7928CA00'], radius: 0.8 })
|
|
101
|
+
* glow.animate({ center: { x: 0.8, y: 0.2 }, radius: 1.2 }, { duration: 2000 })
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function gradient(target: Target, options: GradientOptions): Gradient {
|
|
105
|
+
const view = toView(target)
|
|
106
|
+
if (!options || !types.includes(options.type))
|
|
107
|
+
throw new TypeError(`Gradient type must be 'linear', 'radial' or 'conic'.`)
|
|
108
|
+
const first = resolve(
|
|
109
|
+
{ colors: [], stops: [], angle: 180, x: 0.5, y: 0.5, radius: 0.5 },
|
|
110
|
+
options,
|
|
111
|
+
false,
|
|
112
|
+
)
|
|
113
|
+
return new GradientController(view, options.type, first)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class GradientController implements Gradient {
|
|
117
|
+
private painter: GradientPainter | undefined
|
|
118
|
+
private pending: GradientFrame | undefined
|
|
119
|
+
private removed = false
|
|
120
|
+
private model: GradientFrame
|
|
121
|
+
private running:
|
|
122
|
+
| {
|
|
123
|
+
from: GradientFrame
|
|
124
|
+
to: GradientFrame
|
|
125
|
+
track: Track
|
|
126
|
+
startedAt: number
|
|
127
|
+
handle: { cancel(): void }
|
|
128
|
+
settle: (finished: boolean) => void
|
|
129
|
+
}
|
|
130
|
+
| undefined
|
|
131
|
+
|
|
132
|
+
constructor(
|
|
133
|
+
private readonly view: View,
|
|
134
|
+
readonly type: GradientType,
|
|
135
|
+
frame: GradientFrame,
|
|
136
|
+
) {
|
|
137
|
+
this.model = frame
|
|
138
|
+
this.attach((factory ?? platformPainter)(view, type))
|
|
139
|
+
view.on('unloaded', this.unloaded)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private unloaded = () => this.stop()
|
|
143
|
+
|
|
144
|
+
private attach(painter: GradientPainter | undefined) {
|
|
145
|
+
if (!painter) return
|
|
146
|
+
if (this.removed) return painter.remove()
|
|
147
|
+
this.painter = painter
|
|
148
|
+
painter.paint(this.pending ?? this.model)
|
|
149
|
+
this.pending = undefined
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
animate(values: GradientValues, transition?: Transition | SpringName): Animation {
|
|
153
|
+
this.assertActive()
|
|
154
|
+
const from = this.presented()
|
|
155
|
+
const to = resolve(from, values, true)
|
|
156
|
+
const resolved =
|
|
157
|
+
typeof transition === 'string' ? springOf(transition) : (transition ?? {})
|
|
158
|
+
const track = createTrack(
|
|
159
|
+
'opacity',
|
|
160
|
+
[0, 1],
|
|
161
|
+
resolveTransition(resolved, 'opacity', 2),
|
|
162
|
+
0,
|
|
163
|
+
)
|
|
164
|
+
const reduced = reducedMotion()
|
|
165
|
+
const frameAt = (progress: number) => mix(from, to, progress, reduced)
|
|
166
|
+
this.cancelRunning()
|
|
167
|
+
this.model = frameAt(track.to)
|
|
168
|
+
|
|
169
|
+
let settle!: (finished: boolean) => void
|
|
170
|
+
const finished = new Promise<boolean>((resolve) => (settle = resolve))
|
|
171
|
+
const animation: Animation = {
|
|
172
|
+
finished,
|
|
173
|
+
stop: () => {
|
|
174
|
+
if (this.running?.settle === settle) this.stop()
|
|
175
|
+
},
|
|
176
|
+
then: (onfulfilled, onrejected) => finished.then(onfulfilled, onrejected),
|
|
177
|
+
}
|
|
178
|
+
if (track.end === 0 || !this.painter) {
|
|
179
|
+
this.paint(this.model)
|
|
180
|
+
settle(true)
|
|
181
|
+
return animation
|
|
182
|
+
}
|
|
183
|
+
const handle = this.painter.play(
|
|
184
|
+
track.values.map(frameAt),
|
|
185
|
+
track.times,
|
|
186
|
+
{
|
|
187
|
+
duration: track.duration,
|
|
188
|
+
delay: track.delay,
|
|
189
|
+
repeat: track.repeat,
|
|
190
|
+
reverse: track.reverse,
|
|
191
|
+
},
|
|
192
|
+
(completed) => {
|
|
193
|
+
if (this.running?.settle !== settle) return
|
|
194
|
+
this.running = undefined
|
|
195
|
+
settle(completed)
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
this.running = {
|
|
199
|
+
from,
|
|
200
|
+
to,
|
|
201
|
+
track,
|
|
202
|
+
startedAt: getDriver().now(),
|
|
203
|
+
handle,
|
|
204
|
+
settle,
|
|
205
|
+
}
|
|
206
|
+
return animation
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
set(values: GradientValues) {
|
|
210
|
+
this.assertActive()
|
|
211
|
+
const next = resolve(this.presented(), values, false)
|
|
212
|
+
this.cancelRunning()
|
|
213
|
+
this.model = next
|
|
214
|
+
this.paint(next)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
stop() {
|
|
218
|
+
if (!this.running) return
|
|
219
|
+
const current = this.presented()
|
|
220
|
+
this.cancelRunning()
|
|
221
|
+
this.model = current
|
|
222
|
+
this.paint(current)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
remove() {
|
|
226
|
+
if (this.removed) return
|
|
227
|
+
this.cancelRunning()
|
|
228
|
+
this.removed = true
|
|
229
|
+
this.view.off('unloaded', this.unloaded)
|
|
230
|
+
this.painter?.remove()
|
|
231
|
+
this.painter = undefined
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private paint(frame: GradientFrame) {
|
|
235
|
+
if (this.painter) this.painter.paint(frame)
|
|
236
|
+
else this.pending = frame
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private presented(): GradientFrame {
|
|
240
|
+
const running = this.running
|
|
241
|
+
if (!running) return this.model
|
|
242
|
+
const elapsed = getDriver().now() - running.startedAt
|
|
243
|
+
return mix(running.from, running.to, running.track.valueAt(elapsed), reducedMotion())
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private cancelRunning() {
|
|
247
|
+
const running = this.running
|
|
248
|
+
if (!running) return
|
|
249
|
+
this.running = undefined
|
|
250
|
+
running.handle.cancel()
|
|
251
|
+
running.settle(false)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private assertActive() {
|
|
255
|
+
if (this.removed) throw new Error('This gradient was removed.')
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function springOf(name: SpringName): Transition {
|
|
260
|
+
const spring = springs[name]
|
|
261
|
+
if (!spring || !Object.hasOwn(springs, name))
|
|
262
|
+
throw new Error(`Unknown spring "${name}".`)
|
|
263
|
+
return spring
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Validates values and fills in what they leave out from `base`. */
|
|
267
|
+
function resolve(
|
|
268
|
+
base: GradientFrame,
|
|
269
|
+
values: GradientValues,
|
|
270
|
+
sameCount: boolean,
|
|
271
|
+
): GradientFrame {
|
|
272
|
+
if (!values || typeof values !== 'object')
|
|
273
|
+
throw new TypeError('Gradient values must be an object.')
|
|
274
|
+
const colors = values.colors ? values.colors.map(parseColor) : base.colors
|
|
275
|
+
if (colors.length < 2 || colors.length > 8)
|
|
276
|
+
throw new RangeError('A gradient needs between two and eight colors.')
|
|
277
|
+
if (sameCount && values.colors && colors.length !== base.colors.length)
|
|
278
|
+
throw new RangeError(
|
|
279
|
+
`Animating a gradient keeps its ${base.colors.length} colors; use set() to change the count.`,
|
|
280
|
+
)
|
|
281
|
+
const stops = values.stops
|
|
282
|
+
? [...values.stops]
|
|
283
|
+
: values.colors || base.stops.length !== colors.length
|
|
284
|
+
? colors.map((_, i) => i / (colors.length - 1))
|
|
285
|
+
: base.stops
|
|
286
|
+
if (
|
|
287
|
+
stops.length !== colors.length ||
|
|
288
|
+
stops.some(
|
|
289
|
+
(stop, i) =>
|
|
290
|
+
!Number.isFinite(stop) || stop < 0 || stop > 1 || stop < (stops[i - 1] ?? 0),
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
throw new RangeError(
|
|
294
|
+
'Gradient stops need one value per color, from 0 to 1, without going back.',
|
|
295
|
+
)
|
|
296
|
+
const angle = values.angle ?? base.angle
|
|
297
|
+
const x = values.center?.x ?? base.x
|
|
298
|
+
const y = values.center?.y ?? base.y
|
|
299
|
+
const radius = values.radius ?? base.radius
|
|
300
|
+
for (const [name, value] of [
|
|
301
|
+
['angle', angle],
|
|
302
|
+
['center', x],
|
|
303
|
+
['center', y],
|
|
304
|
+
['radius', radius],
|
|
305
|
+
] as const)
|
|
306
|
+
if (!Number.isFinite(value))
|
|
307
|
+
throw new RangeError(`Invalid gradient ${name}: ${value}.`)
|
|
308
|
+
if (radius < 0)
|
|
309
|
+
throw new RangeError(`Gradient radius must not be negative, got ${radius}.`)
|
|
310
|
+
return { colors, stops, angle, x, y, radius }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** One frame between two states. Geometry follows the curve, overshoot included; colors and stops stay between their ends. */
|
|
314
|
+
export function mix(
|
|
315
|
+
from: GradientFrame,
|
|
316
|
+
to: GradientFrame,
|
|
317
|
+
progress: number,
|
|
318
|
+
reduced = false,
|
|
319
|
+
): GradientFrame {
|
|
320
|
+
const p = Math.min(Math.max(progress, 0), 1)
|
|
321
|
+
const g = reduced ? 1 : progress
|
|
322
|
+
const lerp = (a: number, b: number, t: number) => a + (b - a) * t
|
|
323
|
+
return {
|
|
324
|
+
colors: to.colors.map((color, i) => colorMixer(from.colors[i] ?? color, color)(p)),
|
|
325
|
+
stops: to.stops.map((stop, i) => lerp(from.stops[i] ?? stop, stop, p)),
|
|
326
|
+
angle: lerp(from.angle, to.angle, g),
|
|
327
|
+
x: lerp(from.x, to.x, g),
|
|
328
|
+
y: lerp(from.y, to.y, g),
|
|
329
|
+
radius: Math.max(0, lerp(from.radius, to.radius, g)),
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The points a platform gradient needs, in unit coordinates of a `width` × `height` box.
|
|
335
|
+
* Linear follows CSS: the line runs through the center at `angle` and is long enough
|
|
336
|
+
* for the corners to get the first and last colors.
|
|
337
|
+
*/
|
|
338
|
+
export function gradientPoints(
|
|
339
|
+
type: GradientType,
|
|
340
|
+
frame: GradientFrame,
|
|
341
|
+
width: number,
|
|
342
|
+
height: number,
|
|
343
|
+
) {
|
|
344
|
+
const radians = (frame.angle * Math.PI) / 180
|
|
345
|
+
const sin = Math.sin(radians)
|
|
346
|
+
const cos = Math.cos(radians)
|
|
347
|
+
if (type === 'linear') {
|
|
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
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const center = { x: frame.x, y: frame.y }
|
|
360
|
+
const shorter = Math.min(width, height)
|
|
361
|
+
if (type === 'radial') {
|
|
362
|
+
const r = frame.radius * shorter
|
|
363
|
+
return {
|
|
364
|
+
start: center,
|
|
365
|
+
end: {
|
|
366
|
+
x: center.x + (width > 0 ? r / width : 0),
|
|
367
|
+
y: center.y + (height > 0 ? r / height : 0),
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
start: center,
|
|
373
|
+
end: {
|
|
374
|
+
x: center.x + (width > 0 ? (sin * shorter) / (2 * width) : 0),
|
|
375
|
+
y: center.y + (height > 0 ? (-cos * shorter) / (2 * height) : 0),
|
|
376
|
+
},
|
|
377
|
+
}
|
|
378
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
export { animate, decay, set, stop } from './animate.js'
|
|
2
|
+
export { bind, motionValue, scrollValue } from './motion-value.js'
|
|
3
|
+
export type {
|
|
4
|
+
Bindings,
|
|
5
|
+
DerivedColor,
|
|
6
|
+
DerivedValue,
|
|
7
|
+
MapOptions,
|
|
8
|
+
MotionValue,
|
|
9
|
+
ScrollValue,
|
|
10
|
+
} from './motion-value.js'
|
|
2
11
|
export { sequence } from './sequence.js'
|
|
3
12
|
export type { Step, SequenceOptions, SequencePosition } from './sequence.js'
|
|
4
13
|
export { press } from './press.js'
|
|
5
14
|
export { interactionSurface } from './interaction-surface.js'
|
|
6
15
|
export type { InteractionSurface } from './interaction-surface.js'
|
|
7
16
|
export { drag } from './drag.js'
|
|
8
|
-
export type {
|
|
17
|
+
export type {
|
|
18
|
+
Drag,
|
|
19
|
+
DragOptions,
|
|
20
|
+
DragValueOptions,
|
|
21
|
+
DragPoint,
|
|
22
|
+
DragEvent,
|
|
23
|
+
DragRelease,
|
|
24
|
+
} from './drag.js'
|
|
9
25
|
export { scrollLinked } from './scroll-linked.js'
|
|
10
26
|
export type { ScrollLinkedOptions } from './scroll-linked.js'
|
|
11
27
|
export { animateItems } from './items.js'
|
|
@@ -42,12 +58,27 @@ export type {
|
|
|
42
58
|
SymbolEffectName,
|
|
43
59
|
SymbolEffectOptions,
|
|
44
60
|
} from './symbol-effect.js'
|
|
61
|
+
export { gradient } from './gradient.js'
|
|
62
|
+
export type {
|
|
63
|
+
Gradient,
|
|
64
|
+
GradientOptions,
|
|
65
|
+
GradientType,
|
|
66
|
+
GradientValues,
|
|
67
|
+
} from './gradient.js'
|
|
45
68
|
export { configure, onReducedMotion, reducedMotion } from './config.js'
|
|
46
69
|
export type { JoltConfig } from './config.js'
|
|
47
70
|
export { definePreset, presets } from './presets.js'
|
|
48
71
|
export type { Preset, PresetName, Presets } from './presets.js'
|
|
49
|
-
export { springs } from './transition.js'
|
|
50
|
-
export type {
|
|
72
|
+
export { origamiSpring, springs } from './transition.js'
|
|
73
|
+
export type {
|
|
74
|
+
OrigamiSpring,
|
|
75
|
+
RepeatDelayRange,
|
|
76
|
+
Spring,
|
|
77
|
+
SpringName,
|
|
78
|
+
Timing,
|
|
79
|
+
Transition,
|
|
80
|
+
Tween,
|
|
81
|
+
} from './transition.js'
|
|
51
82
|
export { cubicBezier } from './easing.js'
|
|
52
83
|
export type { CubicBezier, Easing, EasingFunction, EasingName } from './easing.js'
|
|
53
84
|
export type { PlaybackAnimation } from './playback.js'
|
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
|