@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.
Files changed (184) hide show
  1. package/README.md +122 -5
  2. package/dist/animate.d.ts +4 -2
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +48 -11
  5. package/dist/animate.js.map +1 -1
  6. package/dist/config.d.ts +22 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +34 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/driver/android.d.ts.map +1 -1
  11. package/dist/driver/android.js +7 -0
  12. package/dist/driver/android.js.map +1 -1
  13. package/dist/driver/index.d.ts +2 -0
  14. package/dist/driver/index.d.ts.map +1 -1
  15. package/dist/driver/index.js.map +1 -1
  16. package/dist/driver/ios-color.js +1 -1
  17. package/dist/driver/ios-color.js.map +1 -1
  18. package/dist/driver/ios-decoration.js +1 -1
  19. package/dist/driver/ios-decoration.js.map +1 -1
  20. package/dist/driver/ios-playback.d.ts +1 -1
  21. package/dist/driver/ios-playback.d.ts.map +1 -1
  22. package/dist/driver/ios-playback.js +14 -0
  23. package/dist/driver/ios-playback.js.map +1 -1
  24. package/dist/driver/ios.d.ts.map +1 -1
  25. package/dist/driver/ios.js +1 -0
  26. package/dist/driver/ios.js.map +1 -1
  27. package/dist/engine.d.ts +16 -1
  28. package/dist/engine.d.ts.map +1 -1
  29. package/dist/engine.js +33 -1
  30. package/dist/engine.js.map +1 -1
  31. package/dist/image-effect-native.d.ts +26 -0
  32. package/dist/image-effect-native.d.ts.map +1 -0
  33. package/dist/image-effect-native.js +73 -0
  34. package/dist/image-effect-native.js.map +1 -0
  35. package/dist/image-effect.d.ts +50 -0
  36. package/dist/image-effect.d.ts.map +1 -0
  37. package/dist/image-effect.js +290 -0
  38. package/dist/image-effect.js.map +1 -0
  39. package/dist/image-owner.d.ts +3 -0
  40. package/dist/image-owner.d.ts.map +1 -0
  41. package/dist/image-owner.js +11 -0
  42. package/dist/image-owner.js.map +1 -0
  43. package/dist/index.d.ts +15 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +7 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/items.d.ts +20 -0
  48. package/dist/items.d.ts.map +1 -0
  49. package/dist/items.js +109 -0
  50. package/dist/items.js.map +1 -0
  51. package/dist/keyboard.d.ts +17 -0
  52. package/dist/keyboard.d.ts.map +1 -0
  53. package/dist/keyboard.js +116 -0
  54. package/dist/keyboard.js.map +1 -0
  55. package/dist/label.d.ts +7 -0
  56. package/dist/label.d.ts.map +1 -0
  57. package/dist/label.js +60 -0
  58. package/dist/label.js.map +1 -0
  59. package/dist/liquid.d.ts.map +1 -1
  60. package/dist/liquid.js +6 -1
  61. package/dist/liquid.js.map +1 -1
  62. package/dist/loader.d.ts.map +1 -1
  63. package/dist/loader.js +2 -1
  64. package/dist/loader.js.map +1 -1
  65. package/dist/particles.d.ts.map +1 -1
  66. package/dist/particles.js +4 -3
  67. package/dist/particles.js.map +1 -1
  68. package/dist/path.d.ts +12 -0
  69. package/dist/path.d.ts.map +1 -0
  70. package/dist/path.js +90 -0
  71. package/dist/path.js.map +1 -0
  72. package/dist/playback-group.d.ts.map +1 -1
  73. package/dist/playback-group.js +29 -1
  74. package/dist/playback-group.js.map +1 -1
  75. package/dist/press.d.ts.map +1 -1
  76. package/dist/press.js +3 -3
  77. package/dist/press.js.map +1 -1
  78. package/dist/reveal.d.ts.map +1 -1
  79. package/dist/reveal.js +3 -2
  80. package/dist/reveal.js.map +1 -1
  81. package/dist/roll-diff.d.ts +22 -0
  82. package/dist/roll-diff.d.ts.map +1 -0
  83. package/dist/roll-diff.js +36 -0
  84. package/dist/roll-diff.js.map +1 -0
  85. package/dist/roll.d.ts +29 -0
  86. package/dist/roll.d.ts.map +1 -0
  87. package/dist/roll.js +442 -0
  88. package/dist/roll.js.map +1 -0
  89. package/dist/scroll-linked.d.ts +10 -0
  90. package/dist/scroll-linked.d.ts.map +1 -0
  91. package/dist/scroll-linked.js +58 -0
  92. package/dist/scroll-linked.js.map +1 -0
  93. package/dist/sequence.d.ts +10 -5
  94. package/dist/sequence.d.ts.map +1 -1
  95. package/dist/sequence.js +202 -14
  96. package/dist/sequence.js.map +1 -1
  97. package/dist/shimmer.d.ts.map +1 -1
  98. package/dist/shimmer.js +2 -1
  99. package/dist/shimmer.js.map +1 -1
  100. package/dist/svg-path.d.ts +14 -0
  101. package/dist/svg-path.d.ts.map +1 -0
  102. package/dist/svg-path.js +281 -0
  103. package/dist/svg-path.js.map +1 -0
  104. package/dist/symbol-effect.d.ts.map +1 -1
  105. package/dist/symbol-effect.js +4 -3
  106. package/dist/symbol-effect.js.map +1 -1
  107. package/dist/text-layout.d.ts +8 -0
  108. package/dist/text-layout.d.ts.map +1 -1
  109. package/dist/text-layout.js +8 -2
  110. package/dist/text-layout.js.map +1 -1
  111. package/dist/text.d.ts +1 -1
  112. package/dist/text.d.ts.map +1 -1
  113. package/dist/text.js +60 -64
  114. package/dist/text.js.map +1 -1
  115. package/dist/track.d.ts +2 -0
  116. package/dist/track.d.ts.map +1 -1
  117. package/dist/track.js +62 -15
  118. package/dist/track.js.map +1 -1
  119. package/dist/types.d.ts +7 -1
  120. package/dist/types.d.ts.map +1 -1
  121. package/dist/vue/index.d.ts +89 -0
  122. package/dist/vue/index.d.ts.map +1 -0
  123. package/dist/vue/index.js +134 -0
  124. package/dist/vue/index.js.map +1 -0
  125. package/dist/zoom-native.d.ts +30 -0
  126. package/dist/zoom-native.d.ts.map +1 -0
  127. package/dist/zoom-native.js +56 -0
  128. package/dist/zoom-native.js.map +1 -0
  129. package/dist/zoomable.d.ts +48 -0
  130. package/dist/zoomable.d.ts.map +1 -0
  131. package/dist/zoomable.js +249 -0
  132. package/dist/zoomable.js.map +1 -0
  133. package/package.json +16 -3
  134. package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
  135. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
  136. package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
  137. package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
  138. package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
  139. package/platforms/android/proguard-rules.pro +10 -0
  140. package/platforms/ios/build.xcconfig +1 -0
  141. package/platforms/ios/src/JoltImageEffect.h +25 -0
  142. package/platforms/ios/src/JoltImageEffect.mm +242 -0
  143. package/platforms/ios/src/JoltPhotoZoom.h +23 -0
  144. package/platforms/ios/src/JoltPhotoZoom.m +229 -0
  145. package/platforms/ios/src/module.modulemap +5 -0
  146. package/src/android-plugin.d.ts +142 -0
  147. package/src/animate.ts +80 -30
  148. package/src/config.ts +46 -0
  149. package/src/driver/android.ts +14 -0
  150. package/src/driver/index.ts +2 -0
  151. package/src/driver/ios-color.ts +1 -1
  152. package/src/driver/ios-decoration.ts +1 -1
  153. package/src/driver/ios-playback.ts +15 -1
  154. package/src/driver/ios.ts +2 -0
  155. package/src/engine.ts +47 -2
  156. package/src/image-effect-native.ts +129 -0
  157. package/src/image-effect.ts +336 -0
  158. package/src/image-owner.ts +12 -0
  159. package/src/index.ts +20 -1
  160. package/src/ios-plugin.d.ts +59 -0
  161. package/src/items.ts +140 -0
  162. package/src/keyboard.ts +132 -0
  163. package/src/label.ts +76 -0
  164. package/src/liquid.ts +6 -1
  165. package/src/loader.ts +2 -1
  166. package/src/particles.ts +4 -4
  167. package/src/path.ts +124 -0
  168. package/src/playback-group.ts +34 -1
  169. package/src/press.ts +3 -5
  170. package/src/reveal.ts +3 -3
  171. package/src/roll-diff.ts +57 -0
  172. package/src/roll.ts +515 -0
  173. package/src/scroll-linked.ts +73 -0
  174. package/src/sequence.ts +199 -14
  175. package/src/shimmer.ts +2 -1
  176. package/src/svg-path.ts +355 -0
  177. package/src/symbol-effect.ts +4 -3
  178. package/src/text-layout.ts +14 -2
  179. package/src/text.ts +62 -81
  180. package/src/track.ts +89 -24
  181. package/src/types.ts +8 -1
  182. package/src/vue/index.ts +189 -0
  183. package/src/zoom-native.ts +124 -0
  184. 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, delay, repeat, reverse } = transition
54
- const motion =
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 = Math.max(
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 = Math.max(1, Math.ceil((motion.duration / 1000) * SAMPLES_PER_SECOND))
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 keyof Keyframes]?: Transition | SpringName
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
@@ -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
+ }