@rafaelaugusto/jolt 0.1.0-beta.5 → 0.1.0-beta.7
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 +122 -5
- package/dist/animate.d.ts +4 -2
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +48 -11
- package/dist/animate.js.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +34 -0
- package/dist/config.js.map +1 -0
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +7 -0
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/index.d.ts +2 -0
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios-color.js +1 -1
- package/dist/driver/ios-color.js.map +1 -1
- package/dist/driver/ios-decoration.js +1 -1
- package/dist/driver/ios-decoration.js.map +1 -1
- package/dist/driver/ios-playback.d.ts +1 -1
- package/dist/driver/ios-playback.d.ts.map +1 -1
- package/dist/driver/ios-playback.js +14 -0
- package/dist/driver/ios-playback.js.map +1 -1
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +1 -0
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +16 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +33 -1
- package/dist/engine.js.map +1 -1
- package/dist/image-effect-native.d.ts +26 -0
- package/dist/image-effect-native.d.ts.map +1 -0
- package/dist/image-effect-native.js +73 -0
- package/dist/image-effect-native.js.map +1 -0
- package/dist/image-effect.d.ts +50 -0
- package/dist/image-effect.d.ts.map +1 -0
- package/dist/image-effect.js +290 -0
- package/dist/image-effect.js.map +1 -0
- package/dist/image-owner.d.ts +3 -0
- package/dist/image-owner.d.ts.map +1 -0
- package/dist/image-owner.js +11 -0
- package/dist/image-owner.js.map +1 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/items.d.ts +20 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +109 -0
- package/dist/items.js.map +1 -0
- package/dist/keyboard.d.ts +17 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +116 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.d.ts.map +1 -0
- package/dist/label.js +60 -0
- package/dist/label.js.map +1 -0
- package/dist/liquid.d.ts.map +1 -1
- package/dist/liquid.js +6 -1
- package/dist/liquid.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +2 -1
- package/dist/loader.js.map +1 -1
- package/dist/particles.d.ts.map +1 -1
- package/dist/particles.js +4 -3
- package/dist/particles.js.map +1 -1
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +90 -0
- package/dist/path.js.map +1 -0
- package/dist/playback-group.d.ts.map +1 -1
- package/dist/playback-group.js +29 -1
- package/dist/playback-group.js.map +1 -1
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +3 -3
- package/dist/press.js.map +1 -1
- package/dist/reveal.d.ts.map +1 -1
- package/dist/reveal.js +3 -2
- package/dist/reveal.js.map +1 -1
- package/dist/roll-diff.d.ts +22 -0
- package/dist/roll-diff.d.ts.map +1 -0
- package/dist/roll-diff.js +36 -0
- package/dist/roll-diff.js.map +1 -0
- package/dist/roll.d.ts +29 -0
- package/dist/roll.d.ts.map +1 -0
- package/dist/roll.js +442 -0
- package/dist/roll.js.map +1 -0
- package/dist/scroll-linked.d.ts +10 -0
- package/dist/scroll-linked.d.ts.map +1 -0
- package/dist/scroll-linked.js +58 -0
- package/dist/scroll-linked.js.map +1 -0
- package/dist/sequence.d.ts +10 -5
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +202 -14
- package/dist/sequence.js.map +1 -1
- package/dist/shimmer.d.ts.map +1 -1
- package/dist/shimmer.js +2 -1
- package/dist/shimmer.js.map +1 -1
- package/dist/svg-path.d.ts +14 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg-path.js +281 -0
- package/dist/svg-path.js.map +1 -0
- package/dist/symbol-effect.d.ts.map +1 -1
- package/dist/symbol-effect.js +4 -3
- package/dist/symbol-effect.js.map +1 -1
- package/dist/text-layout.d.ts +8 -0
- package/dist/text-layout.d.ts.map +1 -1
- package/dist/text-layout.js +8 -2
- package/dist/text-layout.js.map +1 -1
- package/dist/text.d.ts +1 -1
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +60 -64
- package/dist/text.js.map +1 -1
- package/dist/track.d.ts +2 -0
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +62 -15
- package/dist/track.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +89 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +134 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/zoom-native.d.ts +30 -0
- package/dist/zoom-native.d.ts.map +1 -0
- package/dist/zoom-native.js +56 -0
- package/dist/zoom-native.js.map +1 -0
- package/dist/zoomable.d.ts +48 -0
- package/dist/zoomable.d.ts.map +1 -0
- package/dist/zoomable.js +249 -0
- package/dist/zoomable.js.map +1 -0
- package/package.json +16 -3
- package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
- package/platforms/android/proguard-rules.pro +10 -0
- package/platforms/ios/build.xcconfig +1 -0
- package/platforms/ios/src/JoltImageEffect.h +25 -0
- package/platforms/ios/src/JoltImageEffect.mm +242 -0
- package/platforms/ios/src/JoltPhotoZoom.h +23 -0
- package/platforms/ios/src/JoltPhotoZoom.m +229 -0
- package/platforms/ios/src/module.modulemap +5 -0
- package/src/android-plugin.d.ts +142 -0
- package/src/animate.ts +80 -30
- package/src/config.ts +46 -0
- package/src/driver/android.ts +14 -0
- package/src/driver/index.ts +2 -0
- package/src/driver/ios-color.ts +1 -1
- package/src/driver/ios-decoration.ts +1 -1
- package/src/driver/ios-playback.ts +15 -1
- package/src/driver/ios.ts +2 -0
- package/src/engine.ts +47 -2
- package/src/image-effect-native.ts +129 -0
- package/src/image-effect.ts +336 -0
- package/src/image-owner.ts +12 -0
- package/src/index.ts +20 -1
- package/src/ios-plugin.d.ts +59 -0
- package/src/items.ts +140 -0
- package/src/keyboard.ts +132 -0
- package/src/label.ts +76 -0
- package/src/liquid.ts +6 -1
- package/src/loader.ts +2 -1
- package/src/particles.ts +4 -4
- package/src/path.ts +124 -0
- package/src/playback-group.ts +34 -1
- package/src/press.ts +3 -5
- package/src/reveal.ts +3 -3
- package/src/roll-diff.ts +57 -0
- package/src/roll.ts +515 -0
- package/src/scroll-linked.ts +73 -0
- package/src/sequence.ts +199 -14
- package/src/shimmer.ts +2 -1
- package/src/svg-path.ts +355 -0
- package/src/symbol-effect.ts +4 -3
- package/src/text-layout.ts +14 -2
- package/src/text.ts +62 -81
- package/src/track.ts +89 -24
- package/src/types.ts +8 -1
- package/src/vue/index.ts +189 -0
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
package/src/track.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { precision, type Property } from './properties.js'
|
|
|
5
5
|
import { solveSpring, springPhysics } from './spring.js'
|
|
6
6
|
import type { Curve, ResolvedTransition } from './transition.js'
|
|
7
7
|
import { colorMixer, isColorProperty, type ColorProperty } from './color.js'
|
|
8
|
+
import { reducedMotion, timeScale } from './config.js'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* One property animated over time. The curve is computed here and sampled,
|
|
@@ -50,13 +51,22 @@ export function createTrack(
|
|
|
50
51
|
velocity: number,
|
|
51
52
|
): Track {
|
|
52
53
|
if (isColorProperty(property)) return colorTrack(property, keyframes, transition)
|
|
53
|
-
const { curve,
|
|
54
|
-
|
|
54
|
+
const { curve, repeat, reverse } = transition
|
|
55
|
+
if (property !== 'opacity' && reducedMotion())
|
|
56
|
+
return instantTrack(
|
|
57
|
+
property,
|
|
58
|
+
restingValue(property, keyframes, curve, repeat, reverse),
|
|
59
|
+
)
|
|
60
|
+
const slowdown = 1 / timeScale()
|
|
61
|
+
const delay = transition.delay * slowdown
|
|
62
|
+
const motion = slow(
|
|
55
63
|
curve.type === 'spring'
|
|
56
|
-
? spring(property, keyframes, curve, velocity)
|
|
64
|
+
? spring(property, keyframes, curve, velocity * slowdown, slowdown)
|
|
57
65
|
: curve.type === 'decay'
|
|
58
|
-
? decay(property, keyframes[0]!, curve)
|
|
59
|
-
: tween(keyframes, curve)
|
|
66
|
+
? decay(property, keyframes[0]!, curve, slowdown)
|
|
67
|
+
: tween(keyframes, curve, slowdown),
|
|
68
|
+
slowdown,
|
|
69
|
+
)
|
|
60
70
|
const { duration } = motion
|
|
61
71
|
const first = motion.values[0]!
|
|
62
72
|
const last = motion.values.at(-1)!
|
|
@@ -104,6 +114,50 @@ export function createTrack(
|
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
|
|
117
|
+
/** Already at its end: the engine writes the value and plays nothing. */
|
|
118
|
+
export function instantTrack(property: Property, value: number): Track {
|
|
119
|
+
const to = isDecorationProperty(property) ? clampDecoration(property, value) : value
|
|
120
|
+
return {
|
|
121
|
+
property,
|
|
122
|
+
delay: 0,
|
|
123
|
+
duration: 0,
|
|
124
|
+
repeat: 0,
|
|
125
|
+
reverse: false,
|
|
126
|
+
times: [0, 1],
|
|
127
|
+
values: [to, to],
|
|
128
|
+
to,
|
|
129
|
+
end: 0,
|
|
130
|
+
arrival: 0,
|
|
131
|
+
valueAt: () => to,
|
|
132
|
+
velocityAt: () => 0,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function restingValue(
|
|
137
|
+
property: Property,
|
|
138
|
+
keyframes: readonly number[],
|
|
139
|
+
curve: Curve,
|
|
140
|
+
repeat: number,
|
|
141
|
+
reverse: boolean,
|
|
142
|
+
) {
|
|
143
|
+
const first = keyframes[0]!
|
|
144
|
+
if (!Number.isFinite(repeat) || (reverse && repeat % 2 === 1)) return first
|
|
145
|
+
if (curve.type !== 'decay') return keyframes.at(-1)!
|
|
146
|
+
const landing = solveDecay(
|
|
147
|
+
first,
|
|
148
|
+
curve.velocity,
|
|
149
|
+
curve.friction,
|
|
150
|
+
precision[property],
|
|
151
|
+
).to
|
|
152
|
+
return curve.snap ? closest(curve.snap, landing) : landing
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function closest(stops: readonly number[], target: number) {
|
|
156
|
+
return stops.reduce((best, stop) =>
|
|
157
|
+
Math.abs(stop - target) < Math.abs(best - target) ? stop : best,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
107
161
|
function colorTrack(
|
|
108
162
|
property: ColorProperty,
|
|
109
163
|
colors: readonly number[],
|
|
@@ -156,28 +210,46 @@ function colorTrack(
|
|
|
156
210
|
}
|
|
157
211
|
}
|
|
158
212
|
|
|
213
|
+
/** Plays the same trajectory over a longer or shorter time. */
|
|
214
|
+
function slow(motion: Motion, factor: number): Motion {
|
|
215
|
+
if (factor === 1) return motion
|
|
216
|
+
return {
|
|
217
|
+
...motion,
|
|
218
|
+
duration: motion.duration * factor,
|
|
219
|
+
position: (time) => motion.position(time / factor),
|
|
220
|
+
velocity: (time) => motion.velocity(time / factor) / factor,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Keyframes per second of playback, so slowed curves stay smooth. */
|
|
225
|
+
function samples(duration: number, density: number) {
|
|
226
|
+
return Math.max(
|
|
227
|
+
1,
|
|
228
|
+
Math.min(
|
|
229
|
+
MAX_DECAY_SAMPLES,
|
|
230
|
+
Math.ceil(((duration * density) / 1000) * SAMPLES_PER_SECOND),
|
|
231
|
+
),
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
159
235
|
function decay(
|
|
160
236
|
property: Property,
|
|
161
237
|
from: number,
|
|
162
238
|
curve: Extract<Curve, { type: 'decay' }>,
|
|
239
|
+
density: number,
|
|
163
240
|
): Motion {
|
|
164
241
|
const motion = solveDecay(from, curve.velocity, curve.friction, precision[property])
|
|
165
242
|
if (curve.snap) {
|
|
166
|
-
const to = curve.snap.reduce((closest, stop) =>
|
|
167
|
-
Math.abs(stop - motion.to) < Math.abs(closest - motion.to) ? stop : closest,
|
|
168
|
-
)
|
|
169
243
|
return spring(
|
|
170
244
|
property,
|
|
171
|
-
[from, to],
|
|
245
|
+
[from, closest(curve.snap, motion.to)],
|
|
172
246
|
{ type: 'spring', physics: springPhysics({}), velocity: curve.velocity },
|
|
173
247
|
0,
|
|
248
|
+
density,
|
|
174
249
|
)
|
|
175
250
|
}
|
|
176
251
|
|
|
177
|
-
const steps =
|
|
178
|
-
1,
|
|
179
|
-
Math.min(MAX_DECAY_SAMPLES, Math.ceil((motion.duration / 1000) * SAMPLES_PER_SECOND)),
|
|
180
|
-
)
|
|
252
|
+
const steps = samples(motion.duration, density)
|
|
181
253
|
const times: number[] = []
|
|
182
254
|
const values: number[] = []
|
|
183
255
|
for (let i = 0; i <= steps; i++) {
|
|
@@ -201,6 +273,7 @@ function spring(
|
|
|
201
273
|
[from, to]: readonly number[],
|
|
202
274
|
curve: Extract<Curve, { type: 'spring' }>,
|
|
203
275
|
velocity: number,
|
|
276
|
+
density: number,
|
|
204
277
|
): Motion {
|
|
205
278
|
const motion = solveSpring(
|
|
206
279
|
curve.physics,
|
|
@@ -209,7 +282,7 @@ function spring(
|
|
|
209
282
|
curve.velocity ?? velocity,
|
|
210
283
|
precision[property],
|
|
211
284
|
)
|
|
212
|
-
const steps =
|
|
285
|
+
const steps = samples(motion.duration, density)
|
|
213
286
|
const times: number[] = []
|
|
214
287
|
const values: number[] = []
|
|
215
288
|
for (let i = 0; i <= steps; i++) {
|
|
@@ -222,6 +295,7 @@ function spring(
|
|
|
222
295
|
function tween(
|
|
223
296
|
keyframes: readonly number[],
|
|
224
297
|
curve: Extract<Curve, { type: 'tween' }>,
|
|
298
|
+
density: number,
|
|
225
299
|
): Motion {
|
|
226
300
|
const { duration, ease } = curve
|
|
227
301
|
const offsets = curve.times ?? keyframes.map((_, i) => i / (keyframes.length - 1))
|
|
@@ -245,16 +319,7 @@ function tween(
|
|
|
245
319
|
const from = keyframes[i]!
|
|
246
320
|
const to = keyframes[i + 1]!
|
|
247
321
|
const easing = ease[i]!
|
|
248
|
-
const steps =
|
|
249
|
-
easing === linear || from === to
|
|
250
|
-
? 1
|
|
251
|
-
: Math.max(
|
|
252
|
-
1,
|
|
253
|
-
Math.min(
|
|
254
|
-
MAX_DECAY_SAMPLES,
|
|
255
|
-
Math.ceil(((span * duration) / 1000) * SAMPLES_PER_SECOND),
|
|
256
|
-
),
|
|
257
|
-
)
|
|
322
|
+
const steps = easing === linear || from === to ? 1 : samples(span * duration, density)
|
|
258
323
|
for (let step = 1; step <= steps; step++) {
|
|
259
324
|
times.push(start + (span * step) / steps)
|
|
260
325
|
values.push(step === steps ? to : from + (to - from) * easing(step / steps))
|
package/src/types.ts
CHANGED
|
@@ -7,6 +7,10 @@ import type { ColorKeyframes } from './color.js'
|
|
|
7
7
|
export type KeyframeValue = number | readonly (number | null)[]
|
|
8
8
|
|
|
9
9
|
export interface Keyframes {
|
|
10
|
+
/** One continuous SVG path, expressed as translation coordinates in DIPs. */
|
|
11
|
+
path?: string
|
|
12
|
+
/** Face the path's forward tangent (+X artwork). Requires path. */
|
|
13
|
+
orient?: boolean
|
|
10
14
|
backgroundColor?: ColorKeyframes
|
|
11
15
|
/** Uniform starting edge colors; widths and radii may differ. */
|
|
12
16
|
borderColor?: ColorKeyframes
|
|
@@ -41,6 +45,9 @@ export interface Keyframes {
|
|
|
41
45
|
rotateY?: KeyframeValue
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
/** Properties that can be timed or stopped independently. A path owns x/y and optionally rotate. */
|
|
49
|
+
export type KeyframeProperty = Exclude<keyof Keyframes, 'path' | 'orient'>
|
|
50
|
+
|
|
44
51
|
/** A view, or anything that holds one: a NativeScript-Vue element, an Angular `ElementRef`. */
|
|
45
52
|
export type Target =
|
|
46
53
|
View | { readonly nativeView: View } | { readonly nativeElement: View }
|
|
@@ -66,7 +73,7 @@ export interface Stagger {
|
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
export type AnimateOptions = Transition & {
|
|
69
|
-
[K in
|
|
76
|
+
[K in KeyframeProperty | 'path']?: Transition | SpringName
|
|
70
77
|
} & {
|
|
71
78
|
/** Offset translations/rotations and multiply scale/opacity by their values at the start. */
|
|
72
79
|
relative?: boolean
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseTransition,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
type App,
|
|
6
|
+
type BaseTransitionProps,
|
|
7
|
+
type NSVElement,
|
|
8
|
+
type ObjectDirective,
|
|
9
|
+
type PropType,
|
|
10
|
+
} from 'nativescript-vue'
|
|
11
|
+
import {
|
|
12
|
+
animate,
|
|
13
|
+
press,
|
|
14
|
+
type AnimateOptions,
|
|
15
|
+
type Keyframes,
|
|
16
|
+
type PlaybackAnimation,
|
|
17
|
+
type PresetName,
|
|
18
|
+
type SpringName,
|
|
19
|
+
} from '../index.js'
|
|
20
|
+
|
|
21
|
+
type Host = NSVElement
|
|
22
|
+
type Options = AnimateOptions | SpringName
|
|
23
|
+
|
|
24
|
+
/** A preset name or keyframes, alone or paired with a transition. */
|
|
25
|
+
export type AnimateValue =
|
|
26
|
+
PresetName | Keyframes | readonly [animation: PresetName | Keyframes, options?: Options]
|
|
27
|
+
|
|
28
|
+
/** The pressed keyframes, alone or paired with a transition. */
|
|
29
|
+
export type PressValue = Keyframes | readonly [pressed: Keyframes, options?: Options]
|
|
30
|
+
|
|
31
|
+
export interface TransitionProps {
|
|
32
|
+
/** Played when the child is inserted. */
|
|
33
|
+
enter?: AnimateValue
|
|
34
|
+
/** Played before the child is removed. */
|
|
35
|
+
leave?: AnimateValue
|
|
36
|
+
/** Also play `enter` on the first render. */
|
|
37
|
+
appear?: boolean
|
|
38
|
+
mode?: 'in-out' | 'out-in' | 'default'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const animations = new WeakMap<object, PlaybackAnimation>()
|
|
42
|
+
const removers = new WeakMap<object, () => void>()
|
|
43
|
+
|
|
44
|
+
function split<T>(
|
|
45
|
+
value: T | readonly [T, Options?],
|
|
46
|
+
): [animation: T, options: Options | undefined] {
|
|
47
|
+
return Array.isArray(value)
|
|
48
|
+
? [value[0] as T, value[1] as Options | undefined]
|
|
49
|
+
: [value as T, undefined]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function same(a: unknown, b: unknown): boolean {
|
|
53
|
+
if (Object.is(a, b)) return true
|
|
54
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
|
|
55
|
+
return false
|
|
56
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false
|
|
57
|
+
const keys = Object.keys(a)
|
|
58
|
+
return (
|
|
59
|
+
keys.length === Object.keys(b).length &&
|
|
60
|
+
keys.every((key) =>
|
|
61
|
+
same((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function play(el: Host, value: AnimateValue) {
|
|
67
|
+
const [animation, options] = split<PresetName | Keyframes>(value)
|
|
68
|
+
const handle = animate(el, animation, options)
|
|
69
|
+
animations.set(el, handle)
|
|
70
|
+
return handle
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cancel(el: Host) {
|
|
74
|
+
animations.get(el)?.stop()
|
|
75
|
+
animations.delete(el)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function attach(el: Host, value: PressValue | undefined) {
|
|
79
|
+
const [pressed, options] = value === undefined ? [] : split<Keyframes>(value)
|
|
80
|
+
removers.set(el, press(el, pressed, options))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Plays an animation when the element mounts, and again whenever the value changes.
|
|
85
|
+
*
|
|
86
|
+
* ```vue
|
|
87
|
+
* <Label v-animate="'fadeInUp'" />
|
|
88
|
+
* <Label v-animate="['popIn', { delay: 200 }]" />
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export const vAnimate: ObjectDirective<Host, AnimateValue> = {
|
|
92
|
+
mounted(el, { value }) {
|
|
93
|
+
play(el, value)
|
|
94
|
+
},
|
|
95
|
+
updated(el, { value, oldValue }) {
|
|
96
|
+
if (!same(value, oldValue)) play(el, value)
|
|
97
|
+
},
|
|
98
|
+
unmounted: cancel,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Press feedback while a finger is on the element.
|
|
103
|
+
*
|
|
104
|
+
* ```vue
|
|
105
|
+
* <GridLayout v-press />
|
|
106
|
+
* <GridLayout v-press="{ scale: 0.95 }" />
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export const vPress: ObjectDirective<Host, PressValue | undefined> = {
|
|
110
|
+
mounted(el, { value }) {
|
|
111
|
+
attach(el, value)
|
|
112
|
+
},
|
|
113
|
+
updated(el, { value, oldValue }) {
|
|
114
|
+
if (same(value, oldValue)) return
|
|
115
|
+
removers.get(el)?.()
|
|
116
|
+
attach(el, value)
|
|
117
|
+
},
|
|
118
|
+
unmounted(el) {
|
|
119
|
+
removers.get(el)?.()
|
|
120
|
+
removers.delete(el)
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The hooks `JoltTransition` hands to Vue's `BaseTransition`. */
|
|
125
|
+
export function transitionHooks(props: TransitionProps): BaseTransitionProps<Host> {
|
|
126
|
+
const run = (value: AnimateValue | undefined) => (el: Host, done: () => void) => {
|
|
127
|
+
if (value === undefined) {
|
|
128
|
+
done()
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
const handle = play(el, value)
|
|
132
|
+
void handle.finished.then(() => {
|
|
133
|
+
if (animations.get(el) === handle) animations.delete(el)
|
|
134
|
+
done()
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
const hooks: BaseTransitionProps<Host> = {
|
|
138
|
+
onEnter: run(props.enter),
|
|
139
|
+
onEnterCancelled: cancel,
|
|
140
|
+
onLeave: run(props.leave),
|
|
141
|
+
onLeaveCancelled: cancel,
|
|
142
|
+
}
|
|
143
|
+
if (props.appear) {
|
|
144
|
+
hooks.onAppear = hooks.onEnter
|
|
145
|
+
hooks.onAppearCancelled = cancel
|
|
146
|
+
}
|
|
147
|
+
return hooks
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const value = {
|
|
151
|
+
type: [String, Object, Array] as PropType<AnimateValue>,
|
|
152
|
+
default: undefined,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Animates a child in and out. Vue keeps the element until the leave animation settles.
|
|
157
|
+
*
|
|
158
|
+
* ```vue
|
|
159
|
+
* <JoltTransition enter="popIn" leave="fadeOut">
|
|
160
|
+
* <Label v-if="visible" text="Saved" />
|
|
161
|
+
* </JoltTransition>
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export const JoltTransition = defineComponent({
|
|
165
|
+
name: 'JoltTransition',
|
|
166
|
+
props: {
|
|
167
|
+
enter: value,
|
|
168
|
+
leave: value,
|
|
169
|
+
appear: Boolean,
|
|
170
|
+
mode: { type: String as PropType<TransitionProps['mode']>, default: undefined },
|
|
171
|
+
},
|
|
172
|
+
setup(props, { slots }) {
|
|
173
|
+
return () =>
|
|
174
|
+
h(
|
|
175
|
+
BaseTransition,
|
|
176
|
+
{ ...transitionHooks(props), mode: props.mode, appear: props.appear },
|
|
177
|
+
slots,
|
|
178
|
+
)
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
/** Registers `v-animate`, `v-press` and `JoltTransition` on the app. */
|
|
183
|
+
export function install(app: App) {
|
|
184
|
+
app.directive('animate', vAnimate)
|
|
185
|
+
app.directive('press', vPress)
|
|
186
|
+
app.component('JoltTransition', JoltTransition)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default { install }
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Utils, isIOS, type Image, type View } from '@nativescript/core'
|
|
2
|
+
|
|
3
|
+
export interface ZoomSnapshot {
|
|
4
|
+
x: number
|
|
5
|
+
y: number
|
|
6
|
+
scale: number
|
|
7
|
+
progress: number
|
|
8
|
+
pending: boolean
|
|
9
|
+
}
|
|
10
|
+
export interface ZoomDriver {
|
|
11
|
+
readonly native: object
|
|
12
|
+
readonly state: ZoomSnapshot
|
|
13
|
+
zoom(scale: number, x: number, y: number, animated: boolean): void
|
|
14
|
+
reset(animated: boolean): void
|
|
15
|
+
requestDismiss(): void
|
|
16
|
+
setReduced(value: boolean): void
|
|
17
|
+
setActive(value: boolean): void
|
|
18
|
+
layout(): void
|
|
19
|
+
stop(): void
|
|
20
|
+
}
|
|
21
|
+
export interface ZoomSettings {
|
|
22
|
+
minimum: number
|
|
23
|
+
maximum: number
|
|
24
|
+
doubleTap: number
|
|
25
|
+
dismiss: boolean
|
|
26
|
+
reduced: boolean
|
|
27
|
+
backdrop?: View
|
|
28
|
+
chrome?: View
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createZoomDriver(
|
|
32
|
+
image: Image,
|
|
33
|
+
viewport: View,
|
|
34
|
+
options: ZoomSettings,
|
|
35
|
+
dismiss: (
|
|
36
|
+
x: number,
|
|
37
|
+
y: number,
|
|
38
|
+
scale: number,
|
|
39
|
+
velocity: number,
|
|
40
|
+
progress: number,
|
|
41
|
+
) => void,
|
|
42
|
+
settled: (x: number, y: number, scale: number) => void,
|
|
43
|
+
): ZoomDriver {
|
|
44
|
+
const { minimum, maximum, doubleTap, reduced, backdrop, chrome } = options
|
|
45
|
+
if (isIOS) {
|
|
46
|
+
const native =
|
|
47
|
+
JoltPhotoZoom.attachImageViewportMinimumMaximumDoubleTapDismissReducedBackdropChromeOnDismissOnSettled(
|
|
48
|
+
image.nativeViewProtected,
|
|
49
|
+
viewport.nativeViewProtected,
|
|
50
|
+
minimum,
|
|
51
|
+
maximum,
|
|
52
|
+
doubleTap,
|
|
53
|
+
options.dismiss,
|
|
54
|
+
reduced,
|
|
55
|
+
backdrop?.nativeViewProtected ?? null,
|
|
56
|
+
chrome?.nativeViewProtected ?? null,
|
|
57
|
+
dismiss,
|
|
58
|
+
settled,
|
|
59
|
+
)
|
|
60
|
+
return {
|
|
61
|
+
native,
|
|
62
|
+
get state() {
|
|
63
|
+
return {
|
|
64
|
+
x: native.x,
|
|
65
|
+
y: native.y,
|
|
66
|
+
scale: native.scale,
|
|
67
|
+
progress: native.dismissProgress,
|
|
68
|
+
pending: native.dismissPending,
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
zoom: (s, x, y, a) => native.zoomFocusXFocusYAnimated(s, x, y, a),
|
|
72
|
+
reset: (a) => native.reset(a),
|
|
73
|
+
requestDismiss: () => native.requestDismiss(),
|
|
74
|
+
setReduced: (r) => native.setReduced(r),
|
|
75
|
+
setActive: (a) => native.setActive(a),
|
|
76
|
+
layout: () => native.layout(),
|
|
77
|
+
stop: () => native.stop(),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const density = Utils.layout.getDisplayDensity()
|
|
81
|
+
const source = image.imageSource!
|
|
82
|
+
const angle = Math.abs((source.rotationAngle ?? 0) % 180)
|
|
83
|
+
const swapped = angle > 45 && angle < 135
|
|
84
|
+
const native = new org.rafaelaugusto.jolt.PhotoZoom(
|
|
85
|
+
image.nativeViewProtected,
|
|
86
|
+
viewport.nativeViewProtected,
|
|
87
|
+
swapped ? source.height : source.width,
|
|
88
|
+
swapped ? source.width : source.height,
|
|
89
|
+
minimum,
|
|
90
|
+
maximum,
|
|
91
|
+
doubleTap,
|
|
92
|
+
options.dismiss,
|
|
93
|
+
reduced,
|
|
94
|
+
backdrop?.nativeViewProtected ?? null,
|
|
95
|
+
chrome?.nativeViewProtected ?? null,
|
|
96
|
+
(viewport as View & { touchListener?: android.view.View.OnTouchListener })
|
|
97
|
+
.touchListener ?? null,
|
|
98
|
+
(image as View & { touchListener?: android.view.View.OnTouchListener })
|
|
99
|
+
.touchListener ?? null,
|
|
100
|
+
new org.rafaelaugusto.jolt.PhotoZoom.Listener({
|
|
101
|
+
onDismiss: dismiss,
|
|
102
|
+
onSettled: settled,
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
105
|
+
return {
|
|
106
|
+
native,
|
|
107
|
+
get state() {
|
|
108
|
+
return {
|
|
109
|
+
x: native.getX(),
|
|
110
|
+
y: native.getY(),
|
|
111
|
+
scale: native.getScale(),
|
|
112
|
+
progress: native.getDismissProgress(),
|
|
113
|
+
pending: native.isDismissPending(),
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
zoom: (s, x, y, a) => native.zoom(s, x * density, y * density, a),
|
|
117
|
+
reset: (a) => native.reset(a),
|
|
118
|
+
requestDismiss: () => native.requestDismiss(),
|
|
119
|
+
setReduced: (r) => native.setReduced(r),
|
|
120
|
+
setActive: (a) => native.setActive(a),
|
|
121
|
+
layout: () => {},
|
|
122
|
+
stop: () => native.stop(),
|
|
123
|
+
}
|
|
124
|
+
}
|