react-native-keyboard-controller 1.8.0 → 1.9.0
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 +2 -1
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputLayoutChangedEvent.kt +41 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +9 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt +7 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputLayoutObserver.kt +93 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/{KeyboardAnimationCallback.kt → listeners/KeyboardAnimationCallback.kt} +86 -55
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +16 -8
- package/ios/Extensions.swift +7 -0
- package/ios/KeyboardController.xcodeproj/project.pbxproj +41 -7
- package/ios/events/FocusedInputLayoutChangedEvent.h +16 -0
- package/ios/events/FocusedInputLayoutChangedEvent.m +75 -0
- package/ios/observers/FocusedInputLayoutObserver.swift +136 -0
- package/ios/{KeyboardControllerView.mm → views/KeyboardControllerView.mm} +40 -4
- package/ios/{KeyboardControllerViewManager.mm → views/KeyboardControllerViewManager.mm} +3 -0
- package/ios/{KeyboardControllerViewManager.swift → views/KeyboardControllerViewManager.swift} +14 -0
- package/jest/index.js +16 -0
- package/lib/commonjs/animated.js +18 -4
- package/lib/commonjs/animated.js.map +1 -1
- package/lib/commonjs/bindings.js.map +1 -1
- package/lib/commonjs/bindings.native.js.map +1 -1
- package/lib/commonjs/components/KeyboardAvoidingView/hooks.js.map +1 -1
- package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/commonjs/components/KeyboardStickyView/index.js +44 -0
- package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -0
- package/lib/commonjs/components/index.js +7 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/context.js +6 -3
- package/lib/commonjs/context.js.map +1 -1
- package/lib/commonjs/hooks.js +8 -1
- package/lib/commonjs/hooks.js.map +1 -1
- package/lib/commonjs/index.js +8 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/reanimated.js +5 -2
- package/lib/commonjs/reanimated.js.map +1 -1
- package/lib/commonjs/reanimated.native.js +18 -1
- package/lib/commonjs/reanimated.native.js.map +1 -1
- package/lib/commonjs/replicas.js +1 -1
- package/lib/commonjs/replicas.js.map +1 -1
- package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
- package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
- package/lib/commonjs/specs/NativeStatusBarManagerCompat.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/animated.js +19 -5
- package/lib/module/animated.js.map +1 -1
- package/lib/module/bindings.js.map +1 -1
- package/lib/module/bindings.native.js +1 -1
- package/lib/module/bindings.native.js.map +1 -1
- package/lib/module/components/KeyboardAvoidingView/hooks.js.map +1 -1
- package/lib/module/components/KeyboardAvoidingView/index.js +2 -2
- package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/module/components/KeyboardStickyView/index.js +35 -0
- package/lib/module/components/KeyboardStickyView/index.js.map +1 -0
- package/lib/module/components/index.js +1 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/constants.js.map +1 -1
- package/lib/module/context.js +6 -3
- package/lib/module/context.js.map +1 -1
- package/lib/module/hooks.js +7 -1
- package/lib/module/hooks.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal.js.map +1 -1
- package/lib/module/reanimated.js +3 -1
- package/lib/module/reanimated.js.map +1 -1
- package/lib/module/reanimated.native.js +16 -0
- package/lib/module/reanimated.native.js.map +1 -1
- package/lib/module/replicas.js +2 -2
- package/lib/module/replicas.js.map +1 -1
- package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/module/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
- package/lib/module/specs/NativeKeyboardController.js.map +1 -1
- package/lib/module/specs/NativeStatusBarManagerCompat.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/components/KeyboardAvoidingView/index.d.ts +2 -1
- package/lib/typescript/components/KeyboardStickyView/index.d.ts +20 -0
- package/lib/typescript/components/index.d.ts +1 -0
- package/lib/typescript/context.d.ts +3 -2
- package/lib/typescript/hooks.d.ts +5 -2
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/internal.d.ts +1 -1
- package/lib/typescript/reanimated.d.ts +2 -1
- package/lib/typescript/reanimated.native.d.ts +2 -1
- package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +12 -0
- package/lib/typescript/types.d.ts +16 -0
- package/package.json +9 -28
- package/src/animated.tsx +30 -7
- package/src/bindings.native.ts +1 -1
- package/src/bindings.ts +3 -1
- package/src/components/KeyboardAvoidingView/hooks.ts +1 -0
- package/src/components/KeyboardAvoidingView/index.tsx +8 -10
- package/src/components/KeyboardStickyView/index.tsx +63 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +2 -0
- package/src/context.ts +12 -5
- package/src/hooks.ts +10 -6
- package/src/index.ts +1 -1
- package/src/internal.ts +3 -1
- package/src/reanimated.native.ts +30 -1
- package/src/reanimated.ts +13 -2
- package/src/replicas.ts +2 -2
- package/src/specs/KeyboardControllerViewNativeComponent.ts +15 -1
- package/src/specs/KeyboardGestureAreaNativeComponent.ts +2 -1
- package/src/specs/NativeKeyboardController.ts +2 -1
- package/src/specs/NativeStatusBarManagerCompat.ts +2 -1
- package/src/types.ts +27 -1
- /package/ios/{KeyboardMoveEvent.h → events/KeyboardMoveEvent.h} +0 -0
- /package/ios/{KeyboardMoveEvent.m → events/KeyboardMoveEvent.m} +0 -0
- /package/ios/{KeyboardMovementObserver.swift → observers/KeyboardMovementObserver.swift} +0 -0
- /package/ios/{KeyboardControllerView.h → views/KeyboardControllerView.h} +0 -0
package/README.md
CHANGED
|
@@ -13,7 +13,8 @@ Keyboard manager which works in identical way on both iOS and Android.
|
|
|
13
13
|
- module for changing soft input mode on Android 🤔
|
|
14
14
|
- reanimated support 🚀
|
|
15
15
|
- interactive keyboard dismissing 👆📱
|
|
16
|
-
-
|
|
16
|
+
- prebuilt components (`KeyboardStickyView`, re-worked `KeyboardAvoidingView`) 📚
|
|
17
|
+
- easy focused input information retrieval 📝 🔮
|
|
17
18
|
- works with any navigation library 🧭
|
|
18
19
|
- and more is coming... Stay tuned! 😊
|
|
19
20
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
data class FocusedInputLayoutChangedEventData(
|
|
8
|
+
val x: Double,
|
|
9
|
+
val y: Double,
|
|
10
|
+
val width: Double,
|
|
11
|
+
val height: Double,
|
|
12
|
+
val absoluteX: Double,
|
|
13
|
+
val absoluteY: Double,
|
|
14
|
+
val target: Int,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
class FocusedInputLayoutChangedEvent(
|
|
18
|
+
surfaceId: Int,
|
|
19
|
+
viewId: Int,
|
|
20
|
+
private val event: FocusedInputLayoutChangedEventData,
|
|
21
|
+
) : Event<KeyboardTransitionEvent>(surfaceId, viewId) {
|
|
22
|
+
override fun getEventName() = "topFocusedInputLayoutChanged"
|
|
23
|
+
|
|
24
|
+
// All events for a given view can be coalesced
|
|
25
|
+
override fun getCoalescingKey(): Short = 0
|
|
26
|
+
|
|
27
|
+
override fun getEventData(): WritableMap? = Arguments.createMap().apply {
|
|
28
|
+
putInt("target", event.target)
|
|
29
|
+
putMap(
|
|
30
|
+
"layout",
|
|
31
|
+
Arguments.createMap().apply {
|
|
32
|
+
putDouble("x", event.x)
|
|
33
|
+
putDouble("y", event.y)
|
|
34
|
+
putDouble("width", event.width)
|
|
35
|
+
putDouble("height", event.height)
|
|
36
|
+
putDouble("absoluteX", event.absoluteX)
|
|
37
|
+
putDouble("absoluteY", event.absoluteY)
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt
CHANGED
|
@@ -2,6 +2,15 @@ package com.reactnativekeyboardcontroller.extensions
|
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
6
|
+
import com.facebook.react.uimanager.events.Event
|
|
7
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
5
8
|
|
|
6
9
|
val ThemedReactContext.rootView: View?
|
|
7
10
|
get() = this.currentActivity?.window?.decorView?.rootView
|
|
11
|
+
|
|
12
|
+
fun ThemedReactContext?.dispatchEvent(viewId: Int, event: Event<*>) {
|
|
13
|
+
val eventDispatcher: EventDispatcher? =
|
|
14
|
+
UIManagerHelper.getEventDispatcherForReactTag(this, viewId)
|
|
15
|
+
eventDispatcher?.dispatchEvent(event)
|
|
16
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller.listeners
|
|
2
|
+
|
|
3
|
+
import android.view.View.OnLayoutChangeListener
|
|
4
|
+
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
7
|
+
import com.facebook.react.views.textinput.ReactEditText
|
|
8
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
9
|
+
import com.reactnativekeyboardcontroller.events.FocusedInputLayoutChangedEvent
|
|
10
|
+
import com.reactnativekeyboardcontroller.events.FocusedInputLayoutChangedEventData
|
|
11
|
+
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
|
|
12
|
+
import com.reactnativekeyboardcontroller.extensions.dp
|
|
13
|
+
import com.reactnativekeyboardcontroller.extensions.screenLocation
|
|
14
|
+
|
|
15
|
+
val noFocusedInputEvent = FocusedInputLayoutChangedEventData(
|
|
16
|
+
x = 0.0,
|
|
17
|
+
y = 0.0,
|
|
18
|
+
width = 0.0,
|
|
19
|
+
height = 0.0,
|
|
20
|
+
absoluteX = 0.0,
|
|
21
|
+
absoluteY = 0.0,
|
|
22
|
+
target = -1,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class FocusedInputLayoutObserver(val view: ReactViewGroup, private val context: ThemedReactContext?) {
|
|
26
|
+
// constructor variables
|
|
27
|
+
private val surfaceId = UIManagerHelper.getSurfaceId(view)
|
|
28
|
+
|
|
29
|
+
// state variables
|
|
30
|
+
private var lastFocusedInput: ReactEditText? = null
|
|
31
|
+
private var lastEventDispatched: FocusedInputLayoutChangedEventData = noFocusedInputEvent
|
|
32
|
+
|
|
33
|
+
// listeners
|
|
34
|
+
private val layoutListener =
|
|
35
|
+
OnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
|
36
|
+
this.syncUpLayout()
|
|
37
|
+
}
|
|
38
|
+
private val focusListener = OnGlobalFocusChangeListener { oldFocus, newFocus ->
|
|
39
|
+
// unfocused or focused was changed
|
|
40
|
+
if (newFocus == null || oldFocus != null) {
|
|
41
|
+
lastFocusedInput?.removeOnLayoutChangeListener(layoutListener)
|
|
42
|
+
lastFocusedInput = null
|
|
43
|
+
}
|
|
44
|
+
if (newFocus is ReactEditText) {
|
|
45
|
+
lastFocusedInput = newFocus
|
|
46
|
+
newFocus.addOnLayoutChangeListener(layoutListener)
|
|
47
|
+
this.syncUpLayout()
|
|
48
|
+
}
|
|
49
|
+
// unfocused
|
|
50
|
+
if (newFocus == null) {
|
|
51
|
+
dispatchEventToJS(noFocusedInputEvent)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
init {
|
|
56
|
+
view.viewTreeObserver.addOnGlobalFocusChangeListener(focusListener)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fun syncUpLayout() {
|
|
60
|
+
val input = lastFocusedInput ?: return
|
|
61
|
+
|
|
62
|
+
val (x, y) = input.screenLocation
|
|
63
|
+
val event = FocusedInputLayoutChangedEventData(
|
|
64
|
+
x = input.x.dp,
|
|
65
|
+
y = input.y.dp,
|
|
66
|
+
width = input.width.toFloat().dp,
|
|
67
|
+
height = input.height.toFloat().dp,
|
|
68
|
+
absoluteX = x.toFloat().dp,
|
|
69
|
+
absoluteY = y.toFloat().dp,
|
|
70
|
+
target = input.id,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
dispatchEventToJS(event)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fun destroy() {
|
|
77
|
+
view.viewTreeObserver.removeOnGlobalFocusChangeListener(focusListener)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private fun dispatchEventToJS(event: FocusedInputLayoutChangedEventData) {
|
|
81
|
+
if (event != lastEventDispatched) {
|
|
82
|
+
lastEventDispatched = event
|
|
83
|
+
context.dispatchEvent(
|
|
84
|
+
view.id,
|
|
85
|
+
FocusedInputLayoutChangedEvent(
|
|
86
|
+
surfaceId,
|
|
87
|
+
view.id,
|
|
88
|
+
event = event,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
package com.reactnativekeyboardcontroller
|
|
1
|
+
package com.reactnativekeyboardcontroller.listeners
|
|
2
2
|
|
|
3
3
|
import android.animation.ValueAnimator
|
|
4
4
|
import android.os.Build
|
|
5
5
|
import android.util.Log
|
|
6
6
|
import android.view.View
|
|
7
|
+
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener
|
|
7
8
|
import androidx.core.animation.doOnEnd
|
|
8
9
|
import androidx.core.graphics.Insets
|
|
9
10
|
import androidx.core.view.OnApplyWindowInsetsListener
|
|
@@ -15,11 +16,11 @@ import com.facebook.react.bridge.WritableMap
|
|
|
15
16
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
16
17
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
17
18
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
18
|
-
import com.facebook.react.uimanager.events.Event
|
|
19
|
-
import com.facebook.react.uimanager.events.EventDispatcher
|
|
20
19
|
import com.facebook.react.views.textinput.ReactEditText
|
|
21
20
|
import com.facebook.react.views.view.ReactViewGroup
|
|
21
|
+
import com.reactnativekeyboardcontroller.InteractiveKeyboardProvider
|
|
22
22
|
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
|
|
23
|
+
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
|
|
23
24
|
import com.reactnativekeyboardcontroller.extensions.dp
|
|
24
25
|
import kotlin.math.abs
|
|
25
26
|
|
|
@@ -33,56 +34,65 @@ class KeyboardAnimationCallback(
|
|
|
33
34
|
val context: ThemedReactContext?,
|
|
34
35
|
) : WindowInsetsAnimationCompat.Callback(dispatchMode), OnApplyWindowInsetsListener {
|
|
35
36
|
private val surfaceId = UIManagerHelper.getSurfaceId(view)
|
|
37
|
+
|
|
38
|
+
// state variables
|
|
36
39
|
private var persistentKeyboardHeight = 0.0
|
|
37
40
|
private var isKeyboardVisible = false
|
|
38
41
|
private var isTransitioning = false
|
|
39
42
|
private var duration = 0
|
|
40
43
|
private var viewTagFocused = -1
|
|
41
44
|
|
|
45
|
+
// listeners
|
|
46
|
+
private val focusListener = OnGlobalFocusChangeListener { oldFocus, newFocus ->
|
|
47
|
+
if (newFocus is ReactEditText) {
|
|
48
|
+
viewTagFocused = newFocus.id
|
|
49
|
+
|
|
50
|
+
// keyboard is visible and focus has been changed
|
|
51
|
+
if (this.isKeyboardVisible && oldFocus !== null) {
|
|
52
|
+
// imitate iOS behavior and send two instant start/end events containing an info about new tag
|
|
53
|
+
// 1. onStart/onMove/onEnd can be still dispatched after, if keyboard change size (numeric -> alphabetic type)
|
|
54
|
+
// 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be
|
|
55
|
+
// 100% included in onStart/onMove/onEnd lifecycles, but triggering onStart/onEnd several time
|
|
56
|
+
// can bring breaking changes
|
|
57
|
+
context.dispatchEvent(
|
|
58
|
+
view.id,
|
|
59
|
+
KeyboardTransitionEvent(
|
|
60
|
+
surfaceId,
|
|
61
|
+
view.id,
|
|
62
|
+
"topKeyboardMoveStart",
|
|
63
|
+
this.persistentKeyboardHeight,
|
|
64
|
+
1.0,
|
|
65
|
+
0,
|
|
66
|
+
viewTagFocused,
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
context.dispatchEvent(
|
|
70
|
+
view.id,
|
|
71
|
+
KeyboardTransitionEvent(
|
|
72
|
+
surfaceId,
|
|
73
|
+
view.id,
|
|
74
|
+
"topKeyboardMoveEnd",
|
|
75
|
+
this.persistentKeyboardHeight,
|
|
76
|
+
1.0,
|
|
77
|
+
0,
|
|
78
|
+
viewTagFocused,
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
|
|
82
|
+
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
private var layoutObserver: FocusedInputLayoutObserver? = null
|
|
87
|
+
|
|
42
88
|
init {
|
|
43
89
|
require(persistentInsetTypes and deferredInsetTypes == 0) {
|
|
44
90
|
"persistentInsetTypes and deferredInsetTypes can not contain any of " +
|
|
45
91
|
" same WindowInsetsCompat.Type values"
|
|
46
92
|
}
|
|
47
93
|
|
|
48
|
-
view
|
|
49
|
-
|
|
50
|
-
viewTagFocused = newFocus.id
|
|
51
|
-
|
|
52
|
-
// keyboard is visible and focus has been changed
|
|
53
|
-
if (this.isKeyboardVisible && oldFocus !== null) {
|
|
54
|
-
// imitate iOS behavior and send two instant start/end events containing an info about new tag
|
|
55
|
-
// 1. onStart/onMove/onEnd can be still dispatched after, if keyboard change size (numeric -> alphabetic type)
|
|
56
|
-
// 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be
|
|
57
|
-
// 100% included in onStart/onMove/onEnd lifecycles, but triggering onStart/onEnd several time
|
|
58
|
-
// can bring breaking changes
|
|
59
|
-
this.sendEventToJS(
|
|
60
|
-
KeyboardTransitionEvent(
|
|
61
|
-
surfaceId,
|
|
62
|
-
view.id,
|
|
63
|
-
"topKeyboardMoveStart",
|
|
64
|
-
this.persistentKeyboardHeight,
|
|
65
|
-
1.0,
|
|
66
|
-
0,
|
|
67
|
-
viewTagFocused,
|
|
68
|
-
),
|
|
69
|
-
)
|
|
70
|
-
this.sendEventToJS(
|
|
71
|
-
KeyboardTransitionEvent(
|
|
72
|
-
surfaceId,
|
|
73
|
-
view.id,
|
|
74
|
-
"topKeyboardMoveEnd",
|
|
75
|
-
this.persistentKeyboardHeight,
|
|
76
|
-
1.0,
|
|
77
|
-
0,
|
|
78
|
-
viewTagFocused,
|
|
79
|
-
),
|
|
80
|
-
)
|
|
81
|
-
this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
|
|
82
|
-
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
94
|
+
layoutObserver = FocusedInputLayoutObserver(view = view, context = context)
|
|
95
|
+
view.viewTreeObserver.addOnGlobalFocusChangeListener(focusListener)
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
/**
|
|
@@ -115,8 +125,10 @@ class KeyboardAnimationCallback(
|
|
|
115
125
|
val keyboardHeight = getCurrentKeyboardHeight()
|
|
116
126
|
val duration = DEFAULT_ANIMATION_TIME.toInt()
|
|
117
127
|
|
|
128
|
+
layoutObserver?.syncUpLayout()
|
|
118
129
|
this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
|
|
119
|
-
|
|
130
|
+
context.dispatchEvent(
|
|
131
|
+
view.id,
|
|
120
132
|
KeyboardTransitionEvent(
|
|
121
133
|
surfaceId,
|
|
122
134
|
view.id,
|
|
@@ -128,10 +140,12 @@ class KeyboardAnimationCallback(
|
|
|
128
140
|
),
|
|
129
141
|
)
|
|
130
142
|
|
|
131
|
-
val animation =
|
|
143
|
+
val animation =
|
|
144
|
+
ValueAnimator.ofFloat(this.persistentKeyboardHeight.toFloat(), keyboardHeight.toFloat())
|
|
132
145
|
animation.addUpdateListener { animator ->
|
|
133
146
|
val toValue = animator.animatedValue as Float
|
|
134
|
-
|
|
147
|
+
context.dispatchEvent(
|
|
148
|
+
view.id,
|
|
135
149
|
KeyboardTransitionEvent(
|
|
136
150
|
surfaceId,
|
|
137
151
|
view.id,
|
|
@@ -145,7 +159,8 @@ class KeyboardAnimationCallback(
|
|
|
145
159
|
}
|
|
146
160
|
animation.doOnEnd {
|
|
147
161
|
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
|
|
148
|
-
|
|
162
|
+
context.dispatchEvent(
|
|
163
|
+
view.id,
|
|
149
164
|
KeyboardTransitionEvent(
|
|
150
165
|
surfaceId,
|
|
151
166
|
view.id,
|
|
@@ -180,13 +195,15 @@ class KeyboardAnimationCallback(
|
|
|
180
195
|
this.persistentKeyboardHeight = keyboardHeight
|
|
181
196
|
}
|
|
182
197
|
|
|
198
|
+
layoutObserver?.syncUpLayout()
|
|
183
199
|
this.emitEvent(
|
|
184
200
|
"KeyboardController::" + if (!isKeyboardVisible) "keyboardWillHide" else "keyboardWillShow",
|
|
185
201
|
getEventParams(keyboardHeight),
|
|
186
202
|
)
|
|
187
203
|
|
|
188
204
|
Log.i(TAG, "HEIGHT:: $keyboardHeight TAG:: $viewTagFocused")
|
|
189
|
-
|
|
205
|
+
context.dispatchEvent(
|
|
206
|
+
view.id,
|
|
190
207
|
KeyboardTransitionEvent(
|
|
191
208
|
surfaceId,
|
|
192
209
|
view.id,
|
|
@@ -227,10 +244,24 @@ class KeyboardAnimationCallback(
|
|
|
227
244
|
// do nothing, just log an exception send progress as 0
|
|
228
245
|
Log.w(TAG, "Caught arithmetic exception during `progress` calculation: $e")
|
|
229
246
|
}
|
|
230
|
-
Log.i(
|
|
247
|
+
Log.i(
|
|
248
|
+
TAG,
|
|
249
|
+
"DiffY: $diffY $height $progress ${InteractiveKeyboardProvider.isInteractive} $viewTagFocused",
|
|
250
|
+
)
|
|
231
251
|
|
|
232
252
|
val event = if (InteractiveKeyboardProvider.isInteractive) "topKeyboardMoveInteractive" else "topKeyboardMove"
|
|
233
|
-
|
|
253
|
+
context.dispatchEvent(
|
|
254
|
+
view.id,
|
|
255
|
+
KeyboardTransitionEvent(
|
|
256
|
+
surfaceId,
|
|
257
|
+
view.id,
|
|
258
|
+
event,
|
|
259
|
+
height,
|
|
260
|
+
progress,
|
|
261
|
+
duration,
|
|
262
|
+
viewTagFocused,
|
|
263
|
+
),
|
|
264
|
+
)
|
|
234
265
|
|
|
235
266
|
return insets
|
|
236
267
|
}
|
|
@@ -259,7 +290,8 @@ class KeyboardAnimationCallback(
|
|
|
259
290
|
"KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow",
|
|
260
291
|
getEventParams(keyboardHeight),
|
|
261
292
|
)
|
|
262
|
-
|
|
293
|
+
context.dispatchEvent(
|
|
294
|
+
view.id,
|
|
263
295
|
KeyboardTransitionEvent(
|
|
264
296
|
surfaceId,
|
|
265
297
|
view.id,
|
|
@@ -275,6 +307,11 @@ class KeyboardAnimationCallback(
|
|
|
275
307
|
duration = 0
|
|
276
308
|
}
|
|
277
309
|
|
|
310
|
+
fun destroy() {
|
|
311
|
+
view.viewTreeObserver.removeOnGlobalFocusChangeListener(focusListener)
|
|
312
|
+
layoutObserver?.destroy()
|
|
313
|
+
}
|
|
314
|
+
|
|
278
315
|
private fun isKeyboardVisible(): Boolean {
|
|
279
316
|
val insets = ViewCompat.getRootWindowInsets(view)
|
|
280
317
|
|
|
@@ -290,12 +327,6 @@ class KeyboardAnimationCallback(
|
|
|
290
327
|
return (keyboardHeight - navigationBar).toFloat().dp.coerceAtLeast(0.0)
|
|
291
328
|
}
|
|
292
329
|
|
|
293
|
-
private fun sendEventToJS(event: Event<*>) {
|
|
294
|
-
val eventDispatcher: EventDispatcher? =
|
|
295
|
-
UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
296
|
-
eventDispatcher?.dispatchEvent(event)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
330
|
private fun emitEvent(event: String, params: WritableMap) {
|
|
300
331
|
context?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit(event, params)
|
|
301
332
|
|
|
@@ -33,6 +33,8 @@ class KeyboardControllerViewManagerImpl(mReactContext: ReactApplicationContext)
|
|
|
33
33
|
MapBuilder.of("registrationName", "onKeyboardMoveEnd"),
|
|
34
34
|
"topKeyboardMoveInteractive",
|
|
35
35
|
MapBuilder.of("registrationName", "onKeyboardMoveInteractive"),
|
|
36
|
+
"topFocusedInputLayoutChanged",
|
|
37
|
+
MapBuilder.of("registrationName", "onFocusedInputLayoutChanged"),
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
return map
|
package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt
CHANGED
|
@@ -12,10 +12,10 @@ import androidx.core.view.WindowInsetsAnimationCompat
|
|
|
12
12
|
import androidx.core.view.WindowInsetsCompat
|
|
13
13
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
14
14
|
import com.facebook.react.views.view.ReactViewGroup
|
|
15
|
-
import com.reactnativekeyboardcontroller.KeyboardAnimationCallback
|
|
16
15
|
import com.reactnativekeyboardcontroller.extensions.removeSelf
|
|
17
16
|
import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttached
|
|
18
17
|
import com.reactnativekeyboardcontroller.extensions.rootView
|
|
18
|
+
import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
|
|
19
19
|
|
|
20
20
|
private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
|
|
21
21
|
|
|
@@ -30,6 +30,7 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
|
|
|
30
30
|
// internal class members
|
|
31
31
|
private var eventView: ReactViewGroup? = null
|
|
32
32
|
private var wasMounted = false
|
|
33
|
+
private var callback: KeyboardAnimationCallback? = null
|
|
33
34
|
|
|
34
35
|
// region View lifecycles
|
|
35
36
|
override fun onAttachedToWindow() {
|
|
@@ -47,11 +48,7 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
|
|
|
47
48
|
override fun onDetachedFromWindow() {
|
|
48
49
|
super.onDetachedFromWindow()
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
// otherwise we may face NPE when app is getting opened via universal link
|
|
52
|
-
// see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/242
|
|
53
|
-
// for more details
|
|
54
|
-
Handler(Looper.getMainLooper()).post { this.removeKeyboardCallbacks() }
|
|
51
|
+
this.removeKeyboardCallbacks()
|
|
55
52
|
}
|
|
56
53
|
// endregion
|
|
57
54
|
|
|
@@ -118,7 +115,7 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
|
|
|
118
115
|
val root = this.getContentView()
|
|
119
116
|
root?.addView(eventView)
|
|
120
117
|
|
|
121
|
-
|
|
118
|
+
callback = KeyboardAnimationCallback(
|
|
122
119
|
view = this,
|
|
123
120
|
persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
|
|
124
121
|
deferredInsetTypes = WindowInsetsCompat.Type.ime(),
|
|
@@ -137,7 +134,18 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
|
|
|
137
134
|
}
|
|
138
135
|
|
|
139
136
|
private fun removeKeyboardCallbacks() {
|
|
140
|
-
|
|
137
|
+
callback?.destroy()
|
|
138
|
+
|
|
139
|
+
// capture view into closure, because if `onDetachedFromWindow` and `onAttachedToWindow`
|
|
140
|
+
// dispatched synchronously after each other (open application on Fabric), then `.post`
|
|
141
|
+
// will destroy just newly created view (if we have a reference via `this`)
|
|
142
|
+
// and we'll have a memory leak or zombie-view
|
|
143
|
+
val view = eventView
|
|
144
|
+
// we need to remove view asynchronously from `onDetachedFromWindow` method
|
|
145
|
+
// otherwise we may face NPE when app is getting opened via universal link
|
|
146
|
+
// see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/242
|
|
147
|
+
// for more details
|
|
148
|
+
Handler(Looper.getMainLooper()).post { view.removeSelf() }
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
private fun getContentView(): FitWindowsLinearLayout? {
|
package/ios/Extensions.swift
CHANGED
|
@@ -52,3 +52,10 @@ public extension Optional where Wrapped == UIResponder {
|
|
|
52
52
|
#endif
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
public extension UIView {
|
|
57
|
+
var globalFrame: CGRect? {
|
|
58
|
+
let rootView = UIApplication.shared.keyWindow?.rootViewController?.view
|
|
59
|
+
return superview?.convert(frame, to: rootView)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
/* Begin PBXBuildFile section */
|
|
10
10
|
0807071E2A34807B00C05A19 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0807071D2A34807B00C05A19 /* Extensions.swift */; };
|
|
11
|
+
084AEEC62ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 084AEEC52ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m */; };
|
|
12
|
+
084AEEC82ACF4AB2001A3069 /* FocusedInputLayoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084AEEC72ACF4AB2001A3069 /* FocusedInputLayoutObserver.swift */; };
|
|
11
13
|
F333F8D428996B8D0015B05F /* KeyboardControllerView.mm in Sources */ = {isa = PBXBuildFile; fileRef = F333F8D228996B8D0015B05F /* KeyboardControllerView.mm */; };
|
|
12
14
|
F359D34F28133C26000B6AFE /* KeyboardControllerModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = F359D34E28133C26000B6AFE /* KeyboardControllerModule.mm */; };
|
|
13
15
|
F3626A0728B3FE760021B2D9 /* KeyboardMovementObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3626A0628B3FE760021B2D9 /* KeyboardMovementObserver.swift */; };
|
|
@@ -29,6 +31,9 @@
|
|
|
29
31
|
|
|
30
32
|
/* Begin PBXFileReference section */
|
|
31
33
|
0807071D2A34807B00C05A19 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
|
34
|
+
084AEEC22ACF479A001A3069 /* FocusedInputLayoutChangedEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FocusedInputLayoutChangedEvent.h; sourceTree = "<group>"; };
|
|
35
|
+
084AEEC52ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FocusedInputLayoutChangedEvent.m; sourceTree = "<group>"; };
|
|
36
|
+
084AEEC72ACF4AB2001A3069 /* FocusedInputLayoutObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedInputLayoutObserver.swift; sourceTree = "<group>"; };
|
|
32
37
|
134814201AA4EA6300B7C361 /* libKeyboardController.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libKeyboardController.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
33
38
|
B3E7B5891CC2AC0600A0062D /* KeyboardControllerViewManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyboardControllerViewManager.mm; sourceTree = "<group>"; };
|
|
34
39
|
F333F8D128996B1C0015B05F /* KeyboardControllerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyboardControllerView.h; sourceTree = "<group>"; };
|
|
@@ -53,6 +58,37 @@
|
|
|
53
58
|
/* End PBXFrameworksBuildPhase section */
|
|
54
59
|
|
|
55
60
|
/* Begin PBXGroup section */
|
|
61
|
+
084AEEBF2ACDFBF1001A3069 /* events */ = {
|
|
62
|
+
isa = PBXGroup;
|
|
63
|
+
children = (
|
|
64
|
+
F3F50667289E653B003091D6 /* KeyboardMoveEvent.h */,
|
|
65
|
+
F3F50668289E653B003091D6 /* KeyboardMoveEvent.m */,
|
|
66
|
+
084AEEC22ACF479A001A3069 /* FocusedInputLayoutChangedEvent.h */,
|
|
67
|
+
084AEEC52ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m */,
|
|
68
|
+
);
|
|
69
|
+
path = events;
|
|
70
|
+
sourceTree = "<group>";
|
|
71
|
+
};
|
|
72
|
+
084AEEC02ACDFC2A001A3069 /* observers */ = {
|
|
73
|
+
isa = PBXGroup;
|
|
74
|
+
children = (
|
|
75
|
+
F3626A0628B3FE760021B2D9 /* KeyboardMovementObserver.swift */,
|
|
76
|
+
084AEEC72ACF4AB2001A3069 /* FocusedInputLayoutObserver.swift */,
|
|
77
|
+
);
|
|
78
|
+
path = observers;
|
|
79
|
+
sourceTree = "<group>";
|
|
80
|
+
};
|
|
81
|
+
084AEEC12ACF405C001A3069 /* views */ = {
|
|
82
|
+
isa = PBXGroup;
|
|
83
|
+
children = (
|
|
84
|
+
F333F8D228996B8D0015B05F /* KeyboardControllerView.mm */,
|
|
85
|
+
F333F8D128996B1C0015B05F /* KeyboardControllerView.h */,
|
|
86
|
+
F4FF95D6245B92E800C19C63 /* KeyboardControllerViewManager.swift */,
|
|
87
|
+
B3E7B5891CC2AC0600A0062D /* KeyboardControllerViewManager.mm */,
|
|
88
|
+
);
|
|
89
|
+
path = views;
|
|
90
|
+
sourceTree = "<group>";
|
|
91
|
+
};
|
|
56
92
|
134814211AA4EA7D00B7C361 /* Products */ = {
|
|
57
93
|
isa = PBXGroup;
|
|
58
94
|
children = (
|
|
@@ -64,16 +100,12 @@
|
|
|
64
100
|
58B511D21A9E6C8500147676 = {
|
|
65
101
|
isa = PBXGroup;
|
|
66
102
|
children = (
|
|
103
|
+
084AEEC12ACF405C001A3069 /* views */,
|
|
104
|
+
084AEEC02ACDFC2A001A3069 /* observers */,
|
|
105
|
+
084AEEBF2ACDFBF1001A3069 /* events */,
|
|
67
106
|
0807071D2A34807B00C05A19 /* Extensions.swift */,
|
|
68
|
-
F3F50667289E653B003091D6 /* KeyboardMoveEvent.h */,
|
|
69
|
-
F3F50668289E653B003091D6 /* KeyboardMoveEvent.m */,
|
|
70
|
-
F3626A0628B3FE760021B2D9 /* KeyboardMovementObserver.swift */,
|
|
71
|
-
F333F8D228996B8D0015B05F /* KeyboardControllerView.mm */,
|
|
72
|
-
F333F8D128996B1C0015B05F /* KeyboardControllerView.h */,
|
|
73
107
|
F359D35028133C6F000B6AFE /* KeyboardControllerModule-Header.h */,
|
|
74
108
|
F359D34E28133C26000B6AFE /* KeyboardControllerModule.mm */,
|
|
75
|
-
F4FF95D6245B92E800C19C63 /* KeyboardControllerViewManager.swift */,
|
|
76
|
-
B3E7B5891CC2AC0600A0062D /* KeyboardControllerViewManager.mm */,
|
|
77
109
|
F4FF95D5245B92E700C19C63 /* KeyboardController-Bridging-Header.h */,
|
|
78
110
|
134814211AA4EA7D00B7C361 /* Products */,
|
|
79
111
|
);
|
|
@@ -136,9 +168,11 @@
|
|
|
136
168
|
isa = PBXSourcesBuildPhase;
|
|
137
169
|
buildActionMask = 2147483647;
|
|
138
170
|
files = (
|
|
171
|
+
084AEEC62ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m in Sources */,
|
|
139
172
|
F3626A0728B3FE760021B2D9 /* KeyboardMovementObserver.swift in Sources */,
|
|
140
173
|
0807071E2A34807B00C05A19 /* Extensions.swift in Sources */,
|
|
141
174
|
F359D34F28133C26000B6AFE /* KeyboardControllerModule.mm in Sources */,
|
|
175
|
+
084AEEC82ACF4AB2001A3069 /* FocusedInputLayoutObserver.swift in Sources */,
|
|
142
176
|
F4FF95D7245B92E800C19C63 /* KeyboardControllerViewManager.swift in Sources */,
|
|
143
177
|
F333F8D428996B8D0015B05F /* KeyboardControllerView.mm in Sources */,
|
|
144
178
|
F3F50669289E653B003091D6 /* KeyboardMoveEvent.m in Sources */,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FocusedInputLayoutChangedEvent.h
|
|
3
|
+
// KeyboardController
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 05/10/2023.
|
|
6
|
+
// Copyright © 2023 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#import <Foundation/Foundation.h>
|
|
10
|
+
#import <React/RCTEventDispatcherProtocol.h>
|
|
11
|
+
|
|
12
|
+
@interface FocusedInputLayoutChangedEvent : NSObject <RCTEvent>
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithReactTag:(NSNumber *)reactTag event:(NSObject *)event;
|
|
15
|
+
|
|
16
|
+
@end
|