@rafaelaugusto/jolt 0.1.0-beta.1 → 0.1.0-beta.11
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 +385 -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-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/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-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/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/gradient.d.ts +90 -0
- package/dist/gradient.d.ts.map +1 -0
- package/dist/gradient.js +247 -0
- package/dist/gradient.js.map +1 -0
- 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 +47 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- 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 +43 -0
- package/dist/motion-states.d.ts.map +1 -0
- package/dist/motion-states.js +220 -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 +32 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +58 -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/GradientLayer.java +195 -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 +21 -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 +184 -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-gradient.ts +109 -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-gradient.ts +114 -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/gradient.ts +372 -0
- 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 +75 -4
- 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 +269 -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 +93 -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/keyboard.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { Application, Utils, isAndroid, isIOS, type View } from '@nativescript/core'
|
|
2
|
+
import { stop, toView } from './animate.js'
|
|
3
|
+
import type { EasingFunction, EasingName } from './easing.js'
|
|
4
|
+
import type { Spring, Transition } from './transition.js'
|
|
5
|
+
import type { Target } from './types.js'
|
|
6
|
+
|
|
7
|
+
export interface KeyboardOptions {
|
|
8
|
+
/** DIPs kept between the view's bottom edge and the keyboard. Defaults to zero. */
|
|
9
|
+
offset?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface KeyboardState {
|
|
13
|
+
/** Whether a docked software keyboard covers part of the window. */
|
|
14
|
+
readonly visible: boolean
|
|
15
|
+
/** DIPs of the window the keyboard covers, from its bottom edge. Zero when hidden or floating. */
|
|
16
|
+
readonly height: number
|
|
17
|
+
/** Milliseconds the keyboard takes to get there. Zero when it changes at once. */
|
|
18
|
+
readonly duration: number
|
|
19
|
+
/** The keyboard's own timing, to pass to `animate()` and move in step with it. */
|
|
20
|
+
readonly transition: Transition
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const BEGIN_FROM_CURRENT_STATE = 4
|
|
24
|
+
const IOS_KEYBOARD_CURVE = 7
|
|
25
|
+
// Measured: Core Animation plays the keyboard's spring (stiffness 1000, mass 3) critically damped in 500 ms.
|
|
26
|
+
const iosKeyboard: Spring = {
|
|
27
|
+
type: 'spring',
|
|
28
|
+
stiffness: 1000,
|
|
29
|
+
mass: 3,
|
|
30
|
+
damping: 2 * Math.sqrt(3000),
|
|
31
|
+
}
|
|
32
|
+
const iosCurves: readonly EasingName[] = ['easeInOut', 'easeIn', 'easeOut', 'linear']
|
|
33
|
+
const hidden: KeyboardState = Object.freeze({
|
|
34
|
+
visible: false,
|
|
35
|
+
height: 0,
|
|
36
|
+
duration: 0,
|
|
37
|
+
transition: { duration: 0 },
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const listeners = new Set<(keyboard: KeyboardState) => void>()
|
|
41
|
+
let state = hidden
|
|
42
|
+
let unwatch: (() => void) | undefined
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calls back whenever the keyboard shows, hides or changes height, with how much
|
|
46
|
+
* of the window it covers and its timing. Returns a cleanup function.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* const off = onKeyboard(({ visible, transition }) => {
|
|
50
|
+
* animate(header, { scale: visible ? 0.8 : 1 }, transition)
|
|
51
|
+
* })
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function onKeyboard(callback: (keyboard: KeyboardState) => void): () => void {
|
|
55
|
+
if (typeof callback !== 'function')
|
|
56
|
+
throw new TypeError('onKeyboard() takes a callback.')
|
|
57
|
+
const listener = (keyboard: KeyboardState) => callback(keyboard)
|
|
58
|
+
if (listeners.size === 0) {
|
|
59
|
+
state = hidden
|
|
60
|
+
unwatch = isIOS ? watchIOS() : isAndroid ? watchAndroid() : undefined
|
|
61
|
+
}
|
|
62
|
+
listeners.add(listener)
|
|
63
|
+
return () => {
|
|
64
|
+
if (!listeners.delete(listener) || listeners.size > 0) return
|
|
65
|
+
unwatch?.()
|
|
66
|
+
unwatch = undefined
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The last state reported while someone listens. */
|
|
71
|
+
export function currentKeyboard(): KeyboardState {
|
|
72
|
+
return state
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function update(height: number, duration: number, transition: Transition) {
|
|
76
|
+
if (height === state.height) return
|
|
77
|
+
state = Object.freeze({ visible: height > 0, height, duration, transition })
|
|
78
|
+
for (const listener of [...listeners]) listener(state)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function watchIOS() {
|
|
82
|
+
const name = UIKeyboardWillChangeFrameNotification
|
|
83
|
+
const observer = Application.ios.addNotificationObserver(
|
|
84
|
+
name,
|
|
85
|
+
(notification: NSNotification) => {
|
|
86
|
+
const info = notification.userInfo
|
|
87
|
+
const window = Application.ios.window
|
|
88
|
+
const end = info?.objectForKey(UIKeyboardFrameEndUserInfoKey)?.CGRectValue as
|
|
89
|
+
CGRect | undefined
|
|
90
|
+
if (!info || !window || !end) return
|
|
91
|
+
const frame = window.convertRectFromWindow(end, null)
|
|
92
|
+
const bottom = window.bounds.size.height
|
|
93
|
+
const docked =
|
|
94
|
+
frame.size.height > 0 &&
|
|
95
|
+
frame.origin.y < bottom &&
|
|
96
|
+
frame.origin.y + frame.size.height >= bottom - 0.5
|
|
97
|
+
const reported = numberIn(info, UIKeyboardAnimationDurationUserInfoKey) * 1000
|
|
98
|
+
const curve = numberIn(info, UIKeyboardAnimationCurveUserInfoKey)
|
|
99
|
+
const height = docked ? bottom - frame.origin.y : 0
|
|
100
|
+
if (!reported) return update(height, 0, { duration: 0 })
|
|
101
|
+
if (curve === IOS_KEYBOARD_CURVE) return update(height, 500, iosKeyboard)
|
|
102
|
+
update(height, reported, {
|
|
103
|
+
duration: reported,
|
|
104
|
+
ease: iosCurves[curve] ?? 'easeInOut',
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
return () => Application.ios.removeNotificationObserver(observer, name)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function watchAndroid() {
|
|
112
|
+
if (android.os.Build.VERSION.SDK_INT < 30) return undefined
|
|
113
|
+
const density = Utils.layout.getDisplayDensity()
|
|
114
|
+
let observer: org.rafaelaugusto.jolt.KeyboardObserver | undefined
|
|
115
|
+
let stopped = false
|
|
116
|
+
const attach = () => {
|
|
117
|
+
Application.off(Application.launchEvent, attach)
|
|
118
|
+
if (stopped || observer) return
|
|
119
|
+
const activity =
|
|
120
|
+
Application.android.foregroundActivity ?? Application.android.startActivity
|
|
121
|
+
const root = activity?.getWindow()?.getDecorView()
|
|
122
|
+
if (!root) return
|
|
123
|
+
try {
|
|
124
|
+
observer = new org.rafaelaugusto.jolt.KeyboardObserver(
|
|
125
|
+
root,
|
|
126
|
+
new org.rafaelaugusto.jolt.KeyboardObserver.Listener({
|
|
127
|
+
onChange: (height, duration, curve) =>
|
|
128
|
+
update(
|
|
129
|
+
height / density,
|
|
130
|
+
duration,
|
|
131
|
+
duration
|
|
132
|
+
? { duration, ease: curve ? sampled(curve) : 'linear' }
|
|
133
|
+
: { duration: 0 },
|
|
134
|
+
),
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
} catch {
|
|
138
|
+
// A JS-only update cannot install the native plugin.
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
observer.attach()
|
|
142
|
+
const height = observer.height() / density
|
|
143
|
+
if (height > 0) state = Object.freeze({ ...hidden, visible: true, height })
|
|
144
|
+
}
|
|
145
|
+
if (Application.android.foregroundActivity ?? Application.android.startActivity)
|
|
146
|
+
attach()
|
|
147
|
+
else Application.on(Application.launchEvent, attach)
|
|
148
|
+
return () => {
|
|
149
|
+
stopped = true
|
|
150
|
+
Application.off(Application.launchEvent, attach)
|
|
151
|
+
observer?.detach()
|
|
152
|
+
observer = undefined
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sampled(curve: ArrayLike<number>): EasingFunction {
|
|
157
|
+
const values = Array.from({ length: curve.length }, (_, i) => curve[i]!)
|
|
158
|
+
const last = values.length - 1
|
|
159
|
+
if (last < 1) return (progress) => progress
|
|
160
|
+
return (progress) => {
|
|
161
|
+
const position = Math.min(Math.max(progress, 0), 1) * last
|
|
162
|
+
const index = Math.min(Math.floor(position), last - 1)
|
|
163
|
+
return values[index]! + (values[index + 1]! - values[index]!) * (position - index)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Lifts the view by its overlap with the software keyboard, at the keyboard's
|
|
169
|
+
* own speed and curve, and lets it back down when the keyboard hides.
|
|
170
|
+
* Returns a function that removes the effect.
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* const release = followKeyboard(composer)
|
|
174
|
+
* followKeyboard(sendButton, { offset: 12 })
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export function followKeyboard(
|
|
178
|
+
target: Target,
|
|
179
|
+
options: KeyboardOptions = {},
|
|
180
|
+
): () => void {
|
|
181
|
+
const view = toView(target)
|
|
182
|
+
const offset = options.offset ?? 0
|
|
183
|
+
if (!Number.isFinite(offset)) throw new RangeError('Keyboard offset must be finite.')
|
|
184
|
+
if (isIOS) return followIOS(view, offset)
|
|
185
|
+
if (isAndroid) return followAndroid(view, offset)
|
|
186
|
+
return () => {}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function followIOS(view: View, offset: number) {
|
|
190
|
+
let disposed = false
|
|
191
|
+
let current = 0
|
|
192
|
+
const onKeyboard = (notification: NSNotification) => {
|
|
193
|
+
if (disposed || !view.isLoaded) return
|
|
194
|
+
const native = view.nativeViewProtected as UIView | undefined
|
|
195
|
+
const window = native?.window
|
|
196
|
+
const info = notification.userInfo
|
|
197
|
+
if (!native || !window || !info) return
|
|
198
|
+
const end = info.objectForKey(UIKeyboardFrameEndUserInfoKey)?.CGRectValue as
|
|
199
|
+
CGRect | undefined
|
|
200
|
+
if (!end) return
|
|
201
|
+
const keyboard = window.convertRectFromWindow(end, null)
|
|
202
|
+
const hidden =
|
|
203
|
+
keyboard.size.height <= 0 || keyboard.origin.y >= window.bounds.size.height
|
|
204
|
+
const frame = native.convertRectToView(native.bounds, null)
|
|
205
|
+
const bottom = frame.origin.y + frame.size.height - view.translateY
|
|
206
|
+
const overlap = hidden ? 0 : Math.max(0, bottom + offset - keyboard.origin.y)
|
|
207
|
+
if (overlap === current) return
|
|
208
|
+
current = overlap
|
|
209
|
+
stop(view, ['y'])
|
|
210
|
+
const apply = () => {
|
|
211
|
+
view.translateY = overlap ? -overlap : 0
|
|
212
|
+
}
|
|
213
|
+
const duration = numberIn(info, UIKeyboardAnimationDurationUserInfoKey)
|
|
214
|
+
if (!duration) return apply()
|
|
215
|
+
const curve = numberIn(info, UIKeyboardAnimationCurveUserInfoKey)
|
|
216
|
+
// The keyboard's curve is private; shifted into the options it still applies.
|
|
217
|
+
UIView.animateWithDurationDelayOptionsAnimationsCompletion(
|
|
218
|
+
duration,
|
|
219
|
+
0,
|
|
220
|
+
((curve << 16) | BEGIN_FROM_CURRENT_STATE) as UIViewAnimationOptions,
|
|
221
|
+
apply,
|
|
222
|
+
() => {},
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
const observer = Application.ios.addNotificationObserver(
|
|
226
|
+
UIKeyboardWillChangeFrameNotification,
|
|
227
|
+
onKeyboard,
|
|
228
|
+
)
|
|
229
|
+
const cleanup = () => {
|
|
230
|
+
if (disposed) return
|
|
231
|
+
disposed = true
|
|
232
|
+
Application.ios.removeNotificationObserver(
|
|
233
|
+
observer,
|
|
234
|
+
UIKeyboardWillChangeFrameNotification,
|
|
235
|
+
)
|
|
236
|
+
view.off('unloaded', cleanup)
|
|
237
|
+
if (current) view.translateY = 0
|
|
238
|
+
}
|
|
239
|
+
view.on('unloaded', cleanup)
|
|
240
|
+
return cleanup
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function followAndroid(view: View, offset: number) {
|
|
244
|
+
if (android.os.Build.VERSION.SDK_INT < 30) return () => {}
|
|
245
|
+
const density = Utils.layout.getDisplayDensity()
|
|
246
|
+
let disposed = false
|
|
247
|
+
let follower: org.rafaelaugusto.jolt.KeyboardFollower | undefined
|
|
248
|
+
const attach = () => {
|
|
249
|
+
if (disposed || follower) return
|
|
250
|
+
const native = view.nativeViewProtected as android.view.View | undefined
|
|
251
|
+
if (!native) return
|
|
252
|
+
try {
|
|
253
|
+
follower = new org.rafaelaugusto.jolt.KeyboardFollower(
|
|
254
|
+
native,
|
|
255
|
+
offset * density,
|
|
256
|
+
new org.rafaelaugusto.jolt.KeyboardFollower.Listener({
|
|
257
|
+
onStart: () => stop(view, ['y']),
|
|
258
|
+
onSettled: (translation) => {
|
|
259
|
+
view.translateY = translation / density
|
|
260
|
+
},
|
|
261
|
+
}),
|
|
262
|
+
)
|
|
263
|
+
} catch {
|
|
264
|
+
// A JS-only update cannot install the native plugin. Leave the platform's resize in place.
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
follower.attach()
|
|
268
|
+
}
|
|
269
|
+
const cleanup = () => {
|
|
270
|
+
if (disposed) return
|
|
271
|
+
disposed = true
|
|
272
|
+
view.off('loaded', attach)
|
|
273
|
+
view.off('unloaded', cleanup)
|
|
274
|
+
if (follower) {
|
|
275
|
+
follower.detach()
|
|
276
|
+
follower = undefined
|
|
277
|
+
view.translateY = 0
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
view.on('loaded', attach)
|
|
281
|
+
view.on('unloaded', cleanup)
|
|
282
|
+
if (view.isLoaded) attach()
|
|
283
|
+
return cleanup
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** The runtime hands NSNumber values over as plain numbers. */
|
|
287
|
+
function numberIn(info: NSDictionary<unknown, unknown>, key: string): number {
|
|
288
|
+
const value = info.objectForKey(key) as number | NSNumber | null | undefined
|
|
289
|
+
if (typeof value === 'number') return value
|
|
290
|
+
return value?.doubleValue ?? 0
|
|
291
|
+
}
|
package/src/label.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Label } from '@nativescript/core'
|
|
2
|
+
|
|
3
|
+
const callbacks = new WeakMap<NSObject, (finished: boolean) => void>()
|
|
4
|
+
let Delegate: typeof NSObject | undefined
|
|
5
|
+
const styleEvents = [
|
|
6
|
+
'fontInternalChange',
|
|
7
|
+
'colorChange',
|
|
8
|
+
'textAlignmentChange',
|
|
9
|
+
'textDecorationChange',
|
|
10
|
+
'textTransformChange',
|
|
11
|
+
'letterSpacingChange',
|
|
12
|
+
'lineHeightChange',
|
|
13
|
+
'textShadowChange',
|
|
14
|
+
'textStrokeChange',
|
|
15
|
+
'whiteSpaceChange',
|
|
16
|
+
'textOverflowChange',
|
|
17
|
+
'maxLinesChange',
|
|
18
|
+
'paddingLeftChange',
|
|
19
|
+
'paddingRightChange',
|
|
20
|
+
'paddingTopChange',
|
|
21
|
+
'paddingBottomChange',
|
|
22
|
+
'visibilityChange',
|
|
23
|
+
'directionChange',
|
|
24
|
+
'borderLeftWidthChange',
|
|
25
|
+
'borderRightWidthChange',
|
|
26
|
+
'borderTopWidthChange',
|
|
27
|
+
'borderBottomWidthChange',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
/** Reports text, formatted text and style changes of a Label until the returned function is called. */
|
|
31
|
+
export function watchLabel(view: Label, onChange: () => void): () => void {
|
|
32
|
+
const formatted = view.formattedText
|
|
33
|
+
view.on('textChange', onChange)
|
|
34
|
+
view.on('formattedTextChange', onChange)
|
|
35
|
+
formatted?.on('propertyChange', onChange)
|
|
36
|
+
for (const event of styleEvents) view.style.on(event, onChange)
|
|
37
|
+
return () => {
|
|
38
|
+
view.off('textChange', onChange)
|
|
39
|
+
view.off('formattedTextChange', onChange)
|
|
40
|
+
formatted?.off('propertyChange', onChange)
|
|
41
|
+
for (const event of styleEvents) view.style.off(event, onChange)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A CAAnimationDelegate that reports when its animation stops. Release it with `forgetCompletion`. */
|
|
46
|
+
export function completion(
|
|
47
|
+
callback: (finished: boolean) => void,
|
|
48
|
+
): NSObject & CAAnimationDelegate {
|
|
49
|
+
if (!Delegate) {
|
|
50
|
+
const base = NSObject as typeof NSObject & {
|
|
51
|
+
extend(
|
|
52
|
+
methods: object,
|
|
53
|
+
options: { protocols: unknown[]; name: string },
|
|
54
|
+
): typeof NSObject
|
|
55
|
+
}
|
|
56
|
+
Delegate = base.extend(
|
|
57
|
+
{
|
|
58
|
+
animationDidStopFinished(
|
|
59
|
+
this: NSObject,
|
|
60
|
+
_animation: CAAnimation,
|
|
61
|
+
finished: boolean,
|
|
62
|
+
) {
|
|
63
|
+
callbacks.get(this)?.(finished)
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{ protocols: [CAAnimationDelegate], name: 'JoltLabelAnimationDelegate' },
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
const delegate = Delegate.new()
|
|
70
|
+
callbacks.set(delegate, callback)
|
|
71
|
+
return delegate
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function forgetCompletion(delegate: NSObject) {
|
|
75
|
+
callbacks.delete(delegate)
|
|
76
|
+
}
|