@rafaelaugusto/jolt 0.1.0-beta.1 → 0.1.0-beta.10
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 +367 -18
- package/dist/animate.d.ts +19 -3
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +186 -14
- package/dist/animate.js.map +1 -1
- package/dist/color.d.ts +11 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +73 -0
- package/dist/color.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/decay.d.ts +11 -0
- package/dist/decay.d.ts.map +1 -0
- package/dist/decay.js +22 -0
- package/dist/decay.js.map +1 -0
- package/dist/decoration.d.ts +18 -0
- package/dist/decoration.d.ts.map +1 -0
- package/dist/decoration.js +155 -0
- package/dist/decoration.js.map +1 -0
- package/dist/drag-input.d.ts +15 -0
- package/dist/drag-input.d.ts.map +1 -0
- package/dist/drag-input.js +66 -0
- package/dist/drag-input.js.map +1 -0
- package/dist/drag-physics.d.ts +20 -0
- package/dist/drag-physics.d.ts.map +1 -0
- package/dist/drag-physics.js +75 -0
- package/dist/drag-physics.js.map +1 -0
- package/dist/drag.d.ts +58 -0
- package/dist/drag.d.ts.map +1 -0
- package/dist/drag.js +324 -0
- package/dist/drag.js.map +1 -0
- package/dist/driver/android-color.d.ts +20 -0
- package/dist/driver/android-color.d.ts.map +1 -0
- package/dist/driver/android-color.js +136 -0
- package/dist/driver/android-color.js.map +1 -0
- package/dist/driver/android-decoration.d.ts +22 -0
- package/dist/driver/android-decoration.d.ts.map +1 -0
- package/dist/driver/android-decoration.js +139 -0
- package/dist/driver/android-decoration.js.map +1 -0
- package/dist/driver/android-playback.d.ts +13 -0
- package/dist/driver/android-playback.d.ts.map +1 -0
- package/dist/driver/android-playback.js +141 -0
- package/dist/driver/android-playback.js.map +1 -0
- package/dist/driver/android.d.ts +1 -1
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +165 -13
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/index.d.ts +25 -2
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios-color.d.ts +16 -0
- package/dist/driver/ios-color.d.ts.map +1 -0
- package/dist/driver/ios-color.js +392 -0
- package/dist/driver/ios-color.js.map +1 -0
- package/dist/driver/ios-decoration.d.ts +17 -0
- package/dist/driver/ios-decoration.d.ts.map +1 -0
- package/dist/driver/ios-decoration.js +271 -0
- package/dist/driver/ios-decoration.js.map +1 -0
- package/dist/driver/ios-layout.d.ts +9 -0
- package/dist/driver/ios-layout.d.ts.map +1 -0
- package/dist/driver/ios-layout.js +52 -0
- package/dist/driver/ios-layout.js.map +1 -0
- package/dist/driver/ios-playback.d.ts +10 -0
- package/dist/driver/ios-playback.d.ts.map +1 -0
- package/dist/driver/ios-playback.js +47 -0
- package/dist/driver/ios-playback.js.map +1 -0
- package/dist/driver/ios-shadow.d.ts +6 -0
- package/dist/driver/ios-shadow.d.ts.map +1 -0
- package/dist/driver/ios-shadow.js +52 -0
- package/dist/driver/ios-shadow.js.map +1 -0
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +163 -28
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +75 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +293 -23
- package/dist/engine.js.map +1 -1
- package/dist/haptics.d.ts +21 -0
- package/dist/haptics.d.ts.map +1 -0
- package/dist/haptics.js +120 -0
- package/dist/haptics.js.map +1 -0
- 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 +43 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/interaction-surface.d.ts +12 -0
- package/dist/interaction-surface.d.ts.map +1 -0
- package/dist/interaction-surface.js +36 -0
- package/dist/interaction-surface.js.map +1 -0
- 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 +41 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +256 -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/layout.d.ts +13 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +348 -0
- package/dist/layout.js.map +1 -0
- package/dist/liquid-grid.d.ts +3 -0
- package/dist/liquid-grid.d.ts.map +1 -0
- package/dist/liquid-grid.js +24 -0
- package/dist/liquid-grid.js.map +1 -0
- package/dist/liquid.d.ts +18 -0
- package/dist/liquid.d.ts.map +1 -0
- package/dist/liquid.js +234 -0
- package/dist/liquid.js.map +1 -0
- package/dist/loader.d.ts +19 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +173 -0
- package/dist/loader.js.map +1 -0
- package/dist/motion-scope.d.ts +39 -0
- package/dist/motion-scope.d.ts.map +1 -0
- package/dist/motion-scope.js +238 -0
- package/dist/motion-scope.js.map +1 -0
- package/dist/motion-states.d.ts +36 -0
- package/dist/motion-states.d.ts.map +1 -0
- package/dist/motion-states.js +165 -0
- package/dist/motion-states.js.map +1 -0
- package/dist/particles.d.ts +39 -0
- package/dist/particles.d.ts.map +1 -0
- package/dist/particles.js +265 -0
- package/dist/particles.js.map +1 -0
- 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 +6 -0
- package/dist/playback-group.d.ts.map +1 -0
- package/dist/playback-group.js +141 -0
- package/dist/playback-group.js.map +1 -0
- package/dist/playback.d.ts +25 -0
- package/dist/playback.d.ts.map +1 -0
- package/dist/playback.js +35 -0
- package/dist/playback.js.map +1 -0
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +23 -7
- package/dist/press.js.map +1 -1
- package/dist/properties.d.ts +6 -3
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +26 -2
- package/dist/properties.js.map +1 -1
- package/dist/reveal.d.ts +25 -0
- package/dist/reveal.d.ts.map +1 -0
- package/dist/reveal.js +294 -0
- package/dist/reveal.js.map +1 -0
- 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 +208 -14
- package/dist/sequence.js.map +1 -1
- package/dist/shimmer.d.ts +12 -0
- package/dist/shimmer.d.ts.map +1 -0
- package/dist/shimmer.js +161 -0
- package/dist/shimmer.js.map +1 -0
- 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 +23 -0
- package/dist/symbol-effect.d.ts.map +1 -0
- package/dist/symbol-effect.js +187 -0
- package/dist/symbol-effect.js.map +1 -0
- package/dist/text-layout.d.ts +30 -0
- package/dist/text-layout.d.ts.map +1 -0
- package/dist/text-layout.js +185 -0
- package/dist/text-layout.js.map +1 -0
- package/dist/text.d.ts +17 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +263 -0
- package/dist/text.js.map +1 -0
- package/dist/track.d.ts +3 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +266 -13
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +17 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +41 -6
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +48 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +95 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +163 -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 +29 -5
- package/platforms/android/include.gradle +7 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
- 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/KeyboardObserver.java +97 -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 +19 -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/JoltInteractiveAnimation.h +18 -0
- package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
- package/platforms/ios/src/JoltPhotoZoom.h +23 -0
- package/platforms/ios/src/JoltPhotoZoom.m +229 -0
- package/platforms/ios/src/module.modulemap +6 -0
- package/src/android-plugin.d.ts +155 -0
- package/src/animate.ts +249 -36
- package/src/color.ts +101 -0
- package/src/config.ts +84 -0
- package/src/decay.ts +45 -0
- package/src/decoration.ts +180 -0
- package/src/drag-input.ts +74 -0
- package/src/drag-physics.ts +110 -0
- package/src/drag.ts +395 -0
- package/src/driver/android-color.ts +170 -0
- package/src/driver/android-decoration.ts +182 -0
- package/src/driver/android-playback.ts +157 -0
- package/src/driver/android.ts +208 -20
- package/src/driver/index.ts +37 -2
- package/src/driver/ios-color.ts +441 -0
- package/src/driver/ios-decoration.ts +332 -0
- package/src/driver/ios-layout.ts +63 -0
- package/src/driver/ios-playback.ts +51 -0
- package/src/driver/ios-shadow.ts +50 -0
- package/src/driver/ios.ts +193 -33
- package/src/engine.ts +321 -27
- package/src/haptics.ts +148 -0
- 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 +59 -2
- package/src/interaction-surface.ts +41 -0
- package/src/ios-plugin.d.ts +81 -0
- package/src/items.ts +140 -0
- package/src/keyboard.ts +291 -0
- package/src/label.ts +76 -0
- package/src/layout.ts +408 -0
- package/src/liquid-grid.ts +28 -0
- package/src/liquid.ts +287 -0
- package/src/loader.ts +202 -0
- package/src/motion-scope.ts +279 -0
- package/src/motion-states.ts +215 -0
- package/src/particles.ts +323 -0
- package/src/path.ts +124 -0
- package/src/playback-group.ts +143 -0
- package/src/playback.ts +62 -0
- package/src/press.ts +26 -10
- package/src/properties.ts +32 -6
- package/src/reveal.ts +374 -0
- package/src/roll-diff.ts +57 -0
- package/src/roll.ts +515 -0
- package/src/scroll-linked.ts +73 -0
- package/src/sequence.ts +204 -14
- package/src/shimmer.ts +180 -0
- package/src/svg-path.ts +355 -0
- package/src/symbol-effect.ts +226 -0
- package/src/text-layout.ts +228 -0
- package/src/text.ts +279 -0
- package/src/track.ts +347 -12
- package/src/transition.ts +63 -6
- package/src/types.ts +49 -1
- package/src/vue/index.ts +231 -0
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseTransition,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
onUnmounted,
|
|
6
|
+
onActivated,
|
|
7
|
+
onDeactivated,
|
|
8
|
+
readonly,
|
|
9
|
+
shallowRef,
|
|
10
|
+
type App,
|
|
11
|
+
type BaseTransitionProps,
|
|
12
|
+
type NSVElement,
|
|
13
|
+
type ObjectDirective,
|
|
14
|
+
type PropType,
|
|
15
|
+
type Ref,
|
|
16
|
+
} from 'nativescript-vue'
|
|
17
|
+
import {
|
|
18
|
+
press,
|
|
19
|
+
motionScope,
|
|
20
|
+
onKeyboard,
|
|
21
|
+
onReducedMotion,
|
|
22
|
+
reducedMotion,
|
|
23
|
+
type KeyboardState,
|
|
24
|
+
type MotionScope,
|
|
25
|
+
type AnimateOptions,
|
|
26
|
+
type Keyframes,
|
|
27
|
+
type PlaybackAnimation,
|
|
28
|
+
type PresetName,
|
|
29
|
+
type SpringName,
|
|
30
|
+
} from '../index.js'
|
|
31
|
+
import { currentKeyboard } from '../keyboard.js'
|
|
32
|
+
|
|
33
|
+
type Host = NSVElement
|
|
34
|
+
type Options = AnimateOptions | SpringName
|
|
35
|
+
|
|
36
|
+
/** A preset name or keyframes, alone or paired with a transition. */
|
|
37
|
+
export type AnimateValue =
|
|
38
|
+
PresetName | Keyframes | readonly [animation: PresetName | Keyframes, options?: Options]
|
|
39
|
+
|
|
40
|
+
/** The pressed keyframes, alone or paired with a transition. */
|
|
41
|
+
export type PressValue = Keyframes | readonly [pressed: Keyframes, options?: Options]
|
|
42
|
+
|
|
43
|
+
export interface TransitionProps {
|
|
44
|
+
/** Played when the child is inserted. */
|
|
45
|
+
enter?: AnimateValue
|
|
46
|
+
/** Played before the child is removed. */
|
|
47
|
+
leave?: AnimateValue
|
|
48
|
+
/** Also play `enter` on the first render. */
|
|
49
|
+
appear?: boolean
|
|
50
|
+
mode?: 'in-out' | 'out-in' | 'default'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const animations = new WeakMap<object, PlaybackAnimation>()
|
|
54
|
+
const scopes = new WeakMap<object, MotionScope>()
|
|
55
|
+
const removers = new WeakMap<object, () => void>()
|
|
56
|
+
|
|
57
|
+
function split<T>(
|
|
58
|
+
value: T | readonly [T, Options?],
|
|
59
|
+
): [animation: T, options: Options | undefined] {
|
|
60
|
+
return Array.isArray(value)
|
|
61
|
+
? [value[0] as T, value[1] as Options | undefined]
|
|
62
|
+
: [value as T, undefined]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function same(a: unknown, b: unknown): boolean {
|
|
66
|
+
if (Object.is(a, b)) return true
|
|
67
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
|
|
68
|
+
return false
|
|
69
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false
|
|
70
|
+
const keys = Object.keys(a)
|
|
71
|
+
return (
|
|
72
|
+
keys.length === Object.keys(b).length &&
|
|
73
|
+
keys.every((key) =>
|
|
74
|
+
same((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function play(el: Host, value: AnimateValue) {
|
|
80
|
+
const [animation, options] = split<PresetName | Keyframes>(value)
|
|
81
|
+
let scope = scopes.get(el)
|
|
82
|
+
if (!scope) scopes.set(el, (scope = motionScope()))
|
|
83
|
+
const handle = scope.animate(el, animation, options)
|
|
84
|
+
animations.set(el, handle)
|
|
85
|
+
return handle
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function cancel(el: Host) {
|
|
89
|
+
scopes.get(el)?.dispose()
|
|
90
|
+
scopes.delete(el)
|
|
91
|
+
animations.delete(el)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function attach(el: Host, value: PressValue | undefined) {
|
|
95
|
+
const [pressed, options] = value === undefined ? [] : split<Keyframes>(value)
|
|
96
|
+
removers.set(el, press(el, pressed, options))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Plays an animation when the element mounts, and again whenever the value changes.
|
|
101
|
+
*
|
|
102
|
+
* ```vue
|
|
103
|
+
* <Label v-animate="'fadeInUp'" />
|
|
104
|
+
* <Label v-animate="['popIn', { delay: 200 }]" />
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export const vAnimate: ObjectDirective<Host, AnimateValue> = {
|
|
108
|
+
mounted(el, { value }) {
|
|
109
|
+
play(el, value)
|
|
110
|
+
},
|
|
111
|
+
updated(el, { value, oldValue }) {
|
|
112
|
+
if (!same(value, oldValue)) play(el, value)
|
|
113
|
+
},
|
|
114
|
+
unmounted: cancel,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Press feedback while a finger is on the element.
|
|
119
|
+
*
|
|
120
|
+
* ```vue
|
|
121
|
+
* <GridLayout v-press />
|
|
122
|
+
* <GridLayout v-press="{ scale: 0.95 }" />
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export const vPress: ObjectDirective<Host, PressValue | undefined> = {
|
|
126
|
+
mounted(el, { value }) {
|
|
127
|
+
attach(el, value)
|
|
128
|
+
},
|
|
129
|
+
updated(el, { value, oldValue }) {
|
|
130
|
+
if (same(value, oldValue)) return
|
|
131
|
+
removers.get(el)?.()
|
|
132
|
+
attach(el, value)
|
|
133
|
+
},
|
|
134
|
+
unmounted(el) {
|
|
135
|
+
removers.get(el)?.()
|
|
136
|
+
removers.delete(el)
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The hooks `JoltTransition` hands to Vue's `BaseTransition`. */
|
|
141
|
+
export function transitionHooks(props: TransitionProps): BaseTransitionProps<Host> {
|
|
142
|
+
const run = (value: AnimateValue | undefined) => (el: Host, done: () => void) => {
|
|
143
|
+
if (value === undefined) {
|
|
144
|
+
done()
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
const handle = play(el, value)
|
|
148
|
+
void handle.finished.then(() => {
|
|
149
|
+
if (animations.get(el) === handle) animations.delete(el)
|
|
150
|
+
done()
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
const hooks: BaseTransitionProps<Host> = {
|
|
154
|
+
onEnter: run(props.enter),
|
|
155
|
+
onEnterCancelled: cancel,
|
|
156
|
+
onLeave: run(props.leave),
|
|
157
|
+
onLeaveCancelled: cancel,
|
|
158
|
+
}
|
|
159
|
+
if (props.appear) {
|
|
160
|
+
hooks.onAppear = hooks.onEnter
|
|
161
|
+
hooks.onAppearCancelled = cancel
|
|
162
|
+
}
|
|
163
|
+
return hooks
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const value = {
|
|
167
|
+
type: [String, Object, Array] as PropType<AnimateValue>,
|
|
168
|
+
default: undefined,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Animates a child in and out. Vue keeps the element until the leave animation settles.
|
|
173
|
+
*
|
|
174
|
+
* ```vue
|
|
175
|
+
* <JoltTransition enter="popIn" leave="fadeOut">
|
|
176
|
+
* <Label v-if="visible" text="Saved" />
|
|
177
|
+
* </JoltTransition>
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export const JoltTransition = defineComponent({
|
|
181
|
+
name: 'JoltTransition',
|
|
182
|
+
props: {
|
|
183
|
+
enter: value,
|
|
184
|
+
leave: value,
|
|
185
|
+
appear: Boolean,
|
|
186
|
+
mode: { type: String as PropType<TransitionProps['mode']>, default: undefined },
|
|
187
|
+
},
|
|
188
|
+
setup(props, { slots }) {
|
|
189
|
+
return () =>
|
|
190
|
+
h(
|
|
191
|
+
BaseTransition,
|
|
192
|
+
{ ...transitionHooks(props), mode: props.mode, appear: props.appear },
|
|
193
|
+
slots,
|
|
194
|
+
)
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
/** Registers `v-animate`, `v-press` and `JoltTransition` on the app. */
|
|
199
|
+
export function install(app: App) {
|
|
200
|
+
app.directive('animate', vAnimate)
|
|
201
|
+
app.directive('press', vPress)
|
|
202
|
+
app.component('JoltTransition', JoltTransition)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default { install }
|
|
206
|
+
|
|
207
|
+
/** Call in setup(): suspend cached components and dispose only on actual unmount. */
|
|
208
|
+
export function useMotionScope(): MotionScope {
|
|
209
|
+
const scope = motionScope()
|
|
210
|
+
onUnmounted(() => scope.dispose())
|
|
211
|
+
onDeactivated(() => scope.suspend())
|
|
212
|
+
onActivated(() => scope.resume())
|
|
213
|
+
return scope
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Call in setup(): whether motion is reduced, kept up to date until the component unmounts. */
|
|
217
|
+
export function useReducedMotion(): Readonly<Ref<boolean>> {
|
|
218
|
+
const reduced = shallowRef(reducedMotion())
|
|
219
|
+
const off = onReducedMotion((value) => (reduced.value = value))
|
|
220
|
+
onUnmounted(off)
|
|
221
|
+
return readonly(reduced)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Call in setup(): the keyboard's state, kept up to date until the component unmounts. */
|
|
225
|
+
export function useKeyboard(): Readonly<Ref<KeyboardState>> {
|
|
226
|
+
const keyboard = shallowRef<KeyboardState>(currentKeyboard())
|
|
227
|
+
const off = onKeyboard((next) => (keyboard.value = next))
|
|
228
|
+
keyboard.value = currentKeyboard()
|
|
229
|
+
onUnmounted(off)
|
|
230
|
+
return readonly(keyboard) as Readonly<Ref<KeyboardState>>
|
|
231
|
+
}
|
|
@@ -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
|
+
}
|
package/src/zoomable.ts
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { Application, Image, isIOS, type View } from '@nativescript/core'
|
|
2
|
+
import { stop, toView } from './animate.js'
|
|
3
|
+
import { reducedMotion } from './config.js'
|
|
4
|
+
import { ownImage } from './image-owner.js'
|
|
5
|
+
import { createZoomDriver, type ZoomDriver, type ZoomSnapshot } from './zoom-native.js'
|
|
6
|
+
import type { Target } from './types.js'
|
|
7
|
+
|
|
8
|
+
export type ZoomState = Readonly<ZoomSnapshot>
|
|
9
|
+
export interface PhotoDismissal {
|
|
10
|
+
readonly state: ZoomState
|
|
11
|
+
/** Release velocity in viewport-local DIPs per second. */
|
|
12
|
+
readonly velocity: number
|
|
13
|
+
/** Return to the viewer. False means this intent is obsolete. */
|
|
14
|
+
cancel(): boolean
|
|
15
|
+
/** Release the controller. Navigate only when this returns true. */
|
|
16
|
+
complete(): boolean
|
|
17
|
+
}
|
|
18
|
+
export interface ZoomableOptions {
|
|
19
|
+
/** Direct parent of the Image. Its bounds are the clipping/pointer viewport. */
|
|
20
|
+
viewport?: Target
|
|
21
|
+
/** Relative zoom range. Defaults to [1, 5]; minimum must be >= 1. */
|
|
22
|
+
scale?: readonly [number, number]
|
|
23
|
+
/** Double-tap zoom, clamped into the configured range. Defaults to 2.5. */
|
|
24
|
+
doubleTap?: number
|
|
25
|
+
dismiss?: 'vertical' | false
|
|
26
|
+
/** These views follow dismissal natively; no JS progress callback is needed. */
|
|
27
|
+
backdrop?: Target
|
|
28
|
+
chrome?: Target
|
|
29
|
+
/** One terminal intent per drag/programmatic close. App/router owns navigation. */
|
|
30
|
+
onDismiss?: (dismissal: PhotoDismissal) => void
|
|
31
|
+
/** Called at rest, never on each pointer/frame update. */
|
|
32
|
+
onSettled?: (state: ZoomState) => void
|
|
33
|
+
/** Setup failed after loading; resources are disposed before this callback. */
|
|
34
|
+
onError?: (error: Error) => void
|
|
35
|
+
}
|
|
36
|
+
export interface Zoomable {
|
|
37
|
+
readonly ready: boolean
|
|
38
|
+
readonly error: Error | undefined
|
|
39
|
+
readonly state: ZoomState
|
|
40
|
+
/** Native adapter entry point for native navigation integrations. */
|
|
41
|
+
readonly nativeController: object | undefined
|
|
42
|
+
zoomTo(scale: number, focus?: { x: number; y: number }, animated?: boolean): void
|
|
43
|
+
reset(animated?: boolean): void
|
|
44
|
+
requestDismiss(): void
|
|
45
|
+
cancelDismiss(): void
|
|
46
|
+
dispose(): void
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Makes an Image interactive without per-frame JS. Owns its transforms until disposed. */
|
|
50
|
+
export function zoomable(target: Target, options: ZoomableOptions = {}): Zoomable {
|
|
51
|
+
const image = toView(target)
|
|
52
|
+
if (!(image instanceof Image))
|
|
53
|
+
throw new TypeError('zoomable() expects a NativeScript Image.')
|
|
54
|
+
if (!options || typeof options !== 'object')
|
|
55
|
+
throw new TypeError('Zoom options must be an object.')
|
|
56
|
+
const viewport = options.viewport
|
|
57
|
+
? toView(options.viewport)
|
|
58
|
+
: (image.parent as View | undefined)
|
|
59
|
+
if (!viewport)
|
|
60
|
+
throw new TypeError('A zoom viewport is required before the image has a parent.')
|
|
61
|
+
const scale = options.scale ?? [1, 5]
|
|
62
|
+
if (
|
|
63
|
+
!Array.isArray(scale) ||
|
|
64
|
+
scale.length !== 2 ||
|
|
65
|
+
!scale.every(Number.isFinite) ||
|
|
66
|
+
scale[0]! < 1 ||
|
|
67
|
+
scale[1]! < scale[0]!
|
|
68
|
+
)
|
|
69
|
+
throw new RangeError(
|
|
70
|
+
'Zoom scale must be [minimum, maximum], with 1 <= minimum <= maximum.',
|
|
71
|
+
)
|
|
72
|
+
const doubleTap = options.doubleTap ?? 2.5
|
|
73
|
+
if (!Number.isFinite(doubleTap) || doubleTap < 1)
|
|
74
|
+
throw new RangeError('Double-tap zoom must be a finite number >= 1.')
|
|
75
|
+
if (
|
|
76
|
+
options.dismiss !== undefined &&
|
|
77
|
+
options.dismiss !== false &&
|
|
78
|
+
options.dismiss !== 'vertical'
|
|
79
|
+
)
|
|
80
|
+
throw new TypeError("Dismiss must be 'vertical' or false.")
|
|
81
|
+
for (const callback of [options.onDismiss, options.onSettled, options.onError])
|
|
82
|
+
if (callback !== undefined && typeof callback !== 'function')
|
|
83
|
+
throw new TypeError('Zoom callbacks must be functions.')
|
|
84
|
+
const backdrop = options.backdrop ? toView(options.backdrop) : undefined
|
|
85
|
+
const chrome = options.chrome ? toView(options.chrome) : undefined
|
|
86
|
+
if (
|
|
87
|
+
backdrop === image ||
|
|
88
|
+
chrome === image ||
|
|
89
|
+
backdrop === viewport ||
|
|
90
|
+
chrome === viewport
|
|
91
|
+
)
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
'Dismissal backdrop/chrome must be separate from the image and viewport.',
|
|
94
|
+
)
|
|
95
|
+
if (image.parent && image.parent !== viewport)
|
|
96
|
+
throw new TypeError('The zoom viewport must be the image’s direct parent.')
|
|
97
|
+
if (
|
|
98
|
+
image.rotate ||
|
|
99
|
+
image.rotateX ||
|
|
100
|
+
image.rotateY ||
|
|
101
|
+
image.scaleX <= 0 ||
|
|
102
|
+
image.scaleY <= 0
|
|
103
|
+
)
|
|
104
|
+
throw new TypeError(
|
|
105
|
+
'Zoom supports positive axis-aligned image transforms; rotate an ancestor instead.',
|
|
106
|
+
)
|
|
107
|
+
return new Photo(image, viewport, { ...options, doubleTap, scale, backdrop, chrome })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
class Photo implements Zoomable {
|
|
111
|
+
private failure: Error | undefined
|
|
112
|
+
private suspended = false
|
|
113
|
+
private driver: ZoomDriver | undefined
|
|
114
|
+
private stopped = false
|
|
115
|
+
private queued = false
|
|
116
|
+
private releaseOwnership: () => void = () => {}
|
|
117
|
+
private generation = 0
|
|
118
|
+
private reduceObserver:
|
|
119
|
+
ReturnType<typeof Application.ios.addNotificationObserver> | undefined
|
|
120
|
+
private latest: ZoomSnapshot
|
|
121
|
+
constructor(
|
|
122
|
+
private readonly image: Image,
|
|
123
|
+
private readonly viewport: View,
|
|
124
|
+
private readonly options: Omit<ZoomableOptions, 'backdrop' | 'chrome'> & {
|
|
125
|
+
scale: readonly [number, number]
|
|
126
|
+
doubleTap: number
|
|
127
|
+
backdrop?: View
|
|
128
|
+
chrome?: View
|
|
129
|
+
},
|
|
130
|
+
) {
|
|
131
|
+
this.latest = { x: 0, y: 0, scale: options.scale[0], progress: 0, pending: false }
|
|
132
|
+
this.releaseOwnership = ownImage(image, this.dispose)
|
|
133
|
+
stop(image, ['x', 'y', 'scaleX', 'scaleY'])
|
|
134
|
+
for (const view of [image, viewport]) {
|
|
135
|
+
view.on('loaded', this.schedule)
|
|
136
|
+
view.on('layoutChanged', this.schedule)
|
|
137
|
+
view.on('unloaded', this.dispose)
|
|
138
|
+
}
|
|
139
|
+
image.on('imageSourceChange', this.sourceChanged)
|
|
140
|
+
image.on('stretchChange', this.sourceChanged)
|
|
141
|
+
Application.on(Application.suspendEvent, this.suspend)
|
|
142
|
+
Application.on(Application.resumeEvent, this.resume)
|
|
143
|
+
if (isIOS)
|
|
144
|
+
this.reduceObserver = Application.ios.addNotificationObserver(
|
|
145
|
+
UIAccessibilityReduceMotionStatusDidChangeNotification,
|
|
146
|
+
this.preferences,
|
|
147
|
+
)
|
|
148
|
+
this.schedule()
|
|
149
|
+
}
|
|
150
|
+
get ready() {
|
|
151
|
+
return !!this.driver
|
|
152
|
+
}
|
|
153
|
+
get error() {
|
|
154
|
+
return this.failure
|
|
155
|
+
}
|
|
156
|
+
get state(): ZoomState {
|
|
157
|
+
return { ...(this.driver?.state ?? this.latest) }
|
|
158
|
+
}
|
|
159
|
+
get nativeController() {
|
|
160
|
+
return this.driver?.native
|
|
161
|
+
}
|
|
162
|
+
zoomTo(value: number, focus?: { x: number; y: number }, animated = true) {
|
|
163
|
+
if (
|
|
164
|
+
!Number.isFinite(value) ||
|
|
165
|
+
value < this.options.scale[0] ||
|
|
166
|
+
value > this.options.scale[1]
|
|
167
|
+
)
|
|
168
|
+
throw new RangeError('Zoom must be within the configured scale range.')
|
|
169
|
+
if (focus && (!Number.isFinite(focus.x) || !Number.isFinite(focus.y)))
|
|
170
|
+
throw new RangeError('Zoom focus must be finite.')
|
|
171
|
+
if (this.stopped) return
|
|
172
|
+
const size = this.viewport.getActualSize()
|
|
173
|
+
this.driver?.zoom(
|
|
174
|
+
value,
|
|
175
|
+
focus?.x ?? size.width / 2,
|
|
176
|
+
focus?.y ?? size.height / 2,
|
|
177
|
+
animated,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
reset(animated = true) {
|
|
181
|
+
this.generation++
|
|
182
|
+
this.driver?.reset(animated)
|
|
183
|
+
}
|
|
184
|
+
requestDismiss() {
|
|
185
|
+
this.driver?.requestDismiss()
|
|
186
|
+
}
|
|
187
|
+
cancelDismiss() {
|
|
188
|
+
this.reset(true)
|
|
189
|
+
}
|
|
190
|
+
dispose = () => {
|
|
191
|
+
if (this.stopped) return
|
|
192
|
+
this.stopped = true
|
|
193
|
+
this.generation++
|
|
194
|
+
this.latest = {
|
|
195
|
+
x: 0,
|
|
196
|
+
y: 0,
|
|
197
|
+
scale: this.options.scale[0],
|
|
198
|
+
progress: 0,
|
|
199
|
+
pending: false,
|
|
200
|
+
}
|
|
201
|
+
this.driver?.stop()
|
|
202
|
+
this.driver = undefined
|
|
203
|
+
this.releaseOwnership()
|
|
204
|
+
for (const view of [this.image, this.viewport]) {
|
|
205
|
+
view.off('loaded', this.schedule)
|
|
206
|
+
view.off('layoutChanged', this.schedule)
|
|
207
|
+
view.off('unloaded', this.dispose)
|
|
208
|
+
}
|
|
209
|
+
this.image.off('imageSourceChange', this.sourceChanged)
|
|
210
|
+
this.image.off('stretchChange', this.sourceChanged)
|
|
211
|
+
Application.off(Application.suspendEvent, this.suspend)
|
|
212
|
+
Application.off(Application.resumeEvent, this.resume)
|
|
213
|
+
if (this.reduceObserver)
|
|
214
|
+
Application.ios.removeNotificationObserver(
|
|
215
|
+
this.reduceObserver,
|
|
216
|
+
UIAccessibilityReduceMotionStatusDidChangeNotification,
|
|
217
|
+
)
|
|
218
|
+
this.reduceObserver = undefined
|
|
219
|
+
}
|
|
220
|
+
private dismissed = (
|
|
221
|
+
x: number,
|
|
222
|
+
y: number,
|
|
223
|
+
scale: number,
|
|
224
|
+
velocity: number,
|
|
225
|
+
progress: number,
|
|
226
|
+
) => {
|
|
227
|
+
if (this.stopped) return
|
|
228
|
+
const generation = ++this.generation
|
|
229
|
+
const valid = () => !this.stopped && generation === this.generation
|
|
230
|
+
const dismissal: PhotoDismissal = {
|
|
231
|
+
state: { x, y, scale, progress, pending: true },
|
|
232
|
+
velocity,
|
|
233
|
+
cancel: () => {
|
|
234
|
+
if (!valid()) return false
|
|
235
|
+
this.cancelDismiss()
|
|
236
|
+
return true
|
|
237
|
+
},
|
|
238
|
+
complete: () => {
|
|
239
|
+
if (!valid()) return false
|
|
240
|
+
this.dispose()
|
|
241
|
+
return true
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
if (this.options.onDismiss) this.options.onDismiss(dismissal)
|
|
245
|
+
else dismissal.cancel()
|
|
246
|
+
}
|
|
247
|
+
private settled = (x: number, y: number, scale: number) => {
|
|
248
|
+
if (this.stopped) return
|
|
249
|
+
this.generation++
|
|
250
|
+
this.latest = { x, y, scale, progress: 0, pending: false }
|
|
251
|
+
this.options.onSettled?.({ ...this.latest })
|
|
252
|
+
}
|
|
253
|
+
private sourceChanged = () => {
|
|
254
|
+
this.generation++
|
|
255
|
+
this.driver?.stop()
|
|
256
|
+
this.driver = undefined
|
|
257
|
+
this.schedule()
|
|
258
|
+
}
|
|
259
|
+
private suspend = () => {
|
|
260
|
+
this.suspended = true
|
|
261
|
+
this.generation++
|
|
262
|
+
this.driver?.setActive(false)
|
|
263
|
+
}
|
|
264
|
+
private resume = () => {
|
|
265
|
+
this.suspended = false
|
|
266
|
+
this.preferences()
|
|
267
|
+
this.driver?.setActive(true)
|
|
268
|
+
this.schedule()
|
|
269
|
+
}
|
|
270
|
+
private preferences = () => this.driver?.setReduced(reducedMotion())
|
|
271
|
+
private schedule = () => {
|
|
272
|
+
if (this.stopped || this.queued) return
|
|
273
|
+
this.queued = true
|
|
274
|
+
void Promise.resolve()
|
|
275
|
+
.then(() => {
|
|
276
|
+
this.queued = false
|
|
277
|
+
if (
|
|
278
|
+
this.stopped ||
|
|
279
|
+
this.suspended ||
|
|
280
|
+
!this.image.isLoaded ||
|
|
281
|
+
!this.viewport.isLoaded
|
|
282
|
+
)
|
|
283
|
+
return
|
|
284
|
+
if (this.driver) {
|
|
285
|
+
this.driver.layout()
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if (this.image.parent !== this.viewport)
|
|
289
|
+
throw new TypeError('Zoom viewport must remain the direct image parent.')
|
|
290
|
+
const size = this.viewport.getActualSize()
|
|
291
|
+
const imageSize = this.image.getActualSize(),
|
|
292
|
+
source = this.image.imageSource
|
|
293
|
+
if (
|
|
294
|
+
size.width <= 0 ||
|
|
295
|
+
size.height <= 0 ||
|
|
296
|
+
imageSize.width <= 0 ||
|
|
297
|
+
imageSize.height <= 0 ||
|
|
298
|
+
!source ||
|
|
299
|
+
!Number.isFinite(source.width) ||
|
|
300
|
+
!Number.isFinite(source.height) ||
|
|
301
|
+
source.width <= 0 ||
|
|
302
|
+
source.height <= 0
|
|
303
|
+
)
|
|
304
|
+
return
|
|
305
|
+
if (isIOS && typeof JoltPhotoZoom === 'undefined')
|
|
306
|
+
throw new Error('Rebuild the native app to install Jolt photo gestures.')
|
|
307
|
+
this.driver = createZoomDriver(
|
|
308
|
+
this.image,
|
|
309
|
+
this.viewport,
|
|
310
|
+
{
|
|
311
|
+
minimum: this.options.scale[0],
|
|
312
|
+
maximum: this.options.scale[1],
|
|
313
|
+
doubleTap: this.options.doubleTap,
|
|
314
|
+
dismiss: this.options.dismiss === 'vertical',
|
|
315
|
+
reduced: reducedMotion(),
|
|
316
|
+
backdrop: this.options.backdrop,
|
|
317
|
+
chrome: this.options.chrome,
|
|
318
|
+
},
|
|
319
|
+
this.dismissed,
|
|
320
|
+
this.settled,
|
|
321
|
+
)
|
|
322
|
+
})
|
|
323
|
+
.catch((error: unknown) => {
|
|
324
|
+
if (this.stopped) return
|
|
325
|
+
this.failure = error instanceof Error ? error : new Error(String(error))
|
|
326
|
+
this.dispose()
|
|
327
|
+
this.options.onError?.(this.failure)
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
}
|