react-native-keyboard-controller 1.17.3 → 1.17.4
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/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +15 -4
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +0 -5
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt +13 -4
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +14 -2
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +12 -6
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/overlay/OverKeyboardViewGroup.kt +11 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +13 -3
- package/ios/swizzling/UIResponderSwizzle.swift +57 -10
- package/ios/views/KeyboardGestureAreaManager.mm +1 -0
- package/package.json +1 -1
package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt
CHANGED
|
@@ -18,17 +18,21 @@ class KeyboardControllerViewManager(
|
|
|
18
18
|
private val manager = KeyboardControllerViewManagerImpl(mReactContext)
|
|
19
19
|
private val mDelegate = KeyboardControllerViewManagerDelegate(this)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
override fun getName(): String = KeyboardControllerViewManagerImpl.NAME
|
|
24
|
-
|
|
21
|
+
// region Lifecycle
|
|
25
22
|
override fun createViewInstance(context: ThemedReactContext): ReactViewGroup = manager.createViewInstance(context)
|
|
26
23
|
|
|
24
|
+
override fun invalidate() {
|
|
25
|
+
super.invalidate()
|
|
26
|
+
manager.invalidate()
|
|
27
|
+
}
|
|
28
|
+
|
|
27
29
|
override fun onAfterUpdateTransaction(view: ReactViewGroup) {
|
|
28
30
|
super.onAfterUpdateTransaction(view)
|
|
29
31
|
manager.setEdgeToEdge(view as EdgeToEdgeReactViewGroup)
|
|
30
32
|
}
|
|
33
|
+
// endregion
|
|
31
34
|
|
|
35
|
+
// region Props setters
|
|
32
36
|
@ReactProp(name = "statusBarTranslucent")
|
|
33
37
|
override fun setStatusBarTranslucent(
|
|
34
38
|
view: ReactViewGroup,
|
|
@@ -52,7 +56,14 @@ class KeyboardControllerViewManager(
|
|
|
52
56
|
view: ReactViewGroup,
|
|
53
57
|
value: Boolean,
|
|
54
58
|
) = manager.setEnabled(view as EdgeToEdgeReactViewGroup, value)
|
|
59
|
+
// endregion
|
|
55
60
|
|
|
61
|
+
// region Getters
|
|
56
62
|
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
|
|
57
63
|
manager.getExportedCustomDirectEventTypeConstants()
|
|
64
|
+
|
|
65
|
+
override fun getDelegate(): ViewManagerDelegate<ReactViewGroup> = mDelegate
|
|
66
|
+
|
|
67
|
+
override fun getName(): String = KeyboardControllerViewManagerImpl.NAME
|
|
68
|
+
// endregion
|
|
58
69
|
}
|
package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt
CHANGED
|
@@ -10,13 +10,8 @@ import com.facebook.react.uimanager.ThemedReactContext
|
|
|
10
10
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
11
11
|
import com.facebook.react.uimanager.events.Event
|
|
12
12
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
13
|
-
import com.reactnativekeyboardcontroller.listeners.WindowDimensionListener
|
|
14
13
|
import com.reactnativekeyboardcontroller.log.Logger
|
|
15
14
|
|
|
16
|
-
fun ThemedReactContext.setupWindowDimensionsListener() {
|
|
17
|
-
WindowDimensionListener(this)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
15
|
fun ThemedReactContext?.dispatchEvent(
|
|
21
16
|
viewId: Int,
|
|
22
17
|
event: Event<*>,
|
package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.reactnativekeyboardcontroller.listeners
|
|
2
2
|
|
|
3
3
|
import android.view.ViewGroup
|
|
4
|
+
import android.view.ViewTreeObserver
|
|
4
5
|
import com.facebook.react.bridge.Arguments
|
|
5
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
7
|
import com.reactnativekeyboardcontroller.extensions.content
|
|
@@ -16,8 +17,9 @@ class WindowDimensionListener(
|
|
|
16
17
|
private val context: ThemedReactContext?,
|
|
17
18
|
) {
|
|
18
19
|
private var lastDispatchedDimensions = Dimensions(0.0, 0.0)
|
|
20
|
+
private var layoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
public fun attachListener() {
|
|
21
23
|
// attach to content view only once per app instance
|
|
22
24
|
if (context != null && listenerID != context.hashCode()) {
|
|
23
25
|
listenerID = context.hashCode()
|
|
@@ -26,12 +28,19 @@ class WindowDimensionListener(
|
|
|
26
28
|
|
|
27
29
|
updateWindowDimensions(content)
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
layoutListener =
|
|
32
|
+
ViewTreeObserver.OnGlobalLayoutListener {
|
|
33
|
+
updateWindowDimensions(content)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
content?.viewTreeObserver?.addOnGlobalLayoutListener(layoutListener)
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
public fun detachListener() {
|
|
41
|
+
context?.content?.viewTreeObserver?.removeOnGlobalLayoutListener(layoutListener)
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
private fun updateWindowDimensions(content: ViewGroup?) {
|
|
36
45
|
if (content == null) {
|
|
37
46
|
return
|
|
@@ -7,14 +7,26 @@ import com.reactnativekeyboardcontroller.events.FocusedInputLayoutChangedEvent
|
|
|
7
7
|
import com.reactnativekeyboardcontroller.events.FocusedInputSelectionChangedEvent
|
|
8
8
|
import com.reactnativekeyboardcontroller.events.FocusedInputTextChangedEvent
|
|
9
9
|
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
|
|
10
|
+
import com.reactnativekeyboardcontroller.listeners.WindowDimensionListener
|
|
10
11
|
import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
|
|
11
12
|
|
|
12
13
|
@Suppress("detekt:UnusedPrivateProperty")
|
|
13
14
|
class KeyboardControllerViewManagerImpl(
|
|
14
15
|
mReactContext: ReactApplicationContext,
|
|
15
16
|
) {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
private var listener: WindowDimensionListener? = null
|
|
18
|
+
|
|
19
|
+
fun createViewInstance(reactContext: ThemedReactContext): EdgeToEdgeReactViewGroup {
|
|
20
|
+
if (listener == null) {
|
|
21
|
+
listener = WindowDimensionListener(reactContext)
|
|
22
|
+
listener?.attachListener()
|
|
23
|
+
}
|
|
24
|
+
return EdgeToEdgeReactViewGroup(reactContext)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fun invalidate() {
|
|
28
|
+
listener?.detachListener()
|
|
29
|
+
}
|
|
18
30
|
|
|
19
31
|
fun setEnabled(
|
|
20
32
|
view: EdgeToEdgeReactViewGroup,
|
package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt
CHANGED
|
@@ -15,7 +15,6 @@ import com.reactnativekeyboardcontroller.extensions.content
|
|
|
15
15
|
import com.reactnativekeyboardcontroller.extensions.removeSelf
|
|
16
16
|
import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttached
|
|
17
17
|
import com.reactnativekeyboardcontroller.extensions.rootView
|
|
18
|
-
import com.reactnativekeyboardcontroller.extensions.setupWindowDimensionsListener
|
|
19
18
|
import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
|
|
20
19
|
import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConfig
|
|
21
20
|
import com.reactnativekeyboardcontroller.log.Logger
|
|
@@ -51,7 +50,6 @@ class EdgeToEdgeReactViewGroup(
|
|
|
51
50
|
private val modalAttachedWatcher = ModalAttachedWatcher(this, reactContext, config, ::getKeyboardCallback)
|
|
52
51
|
|
|
53
52
|
init {
|
|
54
|
-
reactContext.setupWindowDimensionsListener()
|
|
55
53
|
tag = VIEW_TAG
|
|
56
54
|
}
|
|
57
55
|
|
|
@@ -65,13 +63,13 @@ class EdgeToEdgeReactViewGroup(
|
|
|
65
63
|
return
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
this.
|
|
66
|
+
this.activate()
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
override fun onDetachedFromWindow() {
|
|
72
70
|
super.onDetachedFromWindow()
|
|
73
71
|
|
|
74
|
-
this.
|
|
72
|
+
this.deactivate()
|
|
75
73
|
}
|
|
76
74
|
|
|
77
75
|
override fun onConfigurationChanged(newConfig: Configuration?) {
|
|
@@ -189,12 +187,20 @@ class EdgeToEdgeReactViewGroup(
|
|
|
189
187
|
// region State managers
|
|
190
188
|
private fun enable() {
|
|
191
189
|
this.setupWindowInsets()
|
|
192
|
-
this.
|
|
193
|
-
modalAttachedWatcher.enable()
|
|
190
|
+
this.activate()
|
|
194
191
|
}
|
|
195
192
|
|
|
196
193
|
private fun disable() {
|
|
197
194
|
this.setupWindowInsets()
|
|
195
|
+
this.deactivate()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private fun activate() {
|
|
199
|
+
this.setupKeyboardCallbacks()
|
|
200
|
+
modalAttachedWatcher.enable()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private fun deactivate() {
|
|
198
204
|
this.removeKeyboardCallbacks()
|
|
199
205
|
modalAttachedWatcher.disable()
|
|
200
206
|
}
|
|
@@ -6,6 +6,7 @@ import android.graphics.PixelFormat
|
|
|
6
6
|
import android.view.MotionEvent
|
|
7
7
|
import android.view.View
|
|
8
8
|
import android.view.WindowManager
|
|
9
|
+
import android.view.accessibility.AccessibilityEvent
|
|
9
10
|
import com.facebook.react.bridge.UiThreadUtil
|
|
10
11
|
import com.facebook.react.bridge.WritableMap
|
|
11
12
|
import com.facebook.react.bridge.WritableNativeMap
|
|
@@ -84,6 +85,16 @@ class OverKeyboardHostView(
|
|
|
84
85
|
}
|
|
85
86
|
// endregion
|
|
86
87
|
|
|
88
|
+
// region Accessibility
|
|
89
|
+
override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {
|
|
90
|
+
// Don’t let Android try to reach children that are not actual descendants
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Explicitly override this to prevent accessibility events being passed down to children
|
|
94
|
+
// Those will be handled by the `hostView` which lives in the `windowManager`
|
|
95
|
+
override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
|
|
96
|
+
// endregion
|
|
97
|
+
|
|
87
98
|
fun show() {
|
|
88
99
|
val layoutParams =
|
|
89
100
|
WindowManager.LayoutParams(
|
package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt
CHANGED
|
@@ -13,16 +13,21 @@ class KeyboardControllerViewManager(
|
|
|
13
13
|
) : ReactViewManager() {
|
|
14
14
|
private val manager = KeyboardControllerViewManagerImpl(mReactContext)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// region Lifecycle
|
|
17
|
+
override fun createViewInstance(context: ThemedReactContext): ReactViewGroup = manager.createViewInstance(context)
|
|
17
18
|
|
|
18
|
-
override fun
|
|
19
|
-
|
|
19
|
+
override fun invalidate() {
|
|
20
|
+
super.invalidate()
|
|
21
|
+
manager.invalidate()
|
|
22
|
+
}
|
|
20
23
|
|
|
21
24
|
override fun onAfterUpdateTransaction(view: ReactViewGroup) {
|
|
22
25
|
super.onAfterUpdateTransaction(view)
|
|
23
26
|
manager.setEdgeToEdge(view as EdgeToEdgeReactViewGroup)
|
|
24
27
|
}
|
|
28
|
+
// endregion
|
|
25
29
|
|
|
30
|
+
// region Props setters
|
|
26
31
|
@ReactProp(name = "enabled")
|
|
27
32
|
fun setEnabled(
|
|
28
33
|
view: EdgeToEdgeReactViewGroup,
|
|
@@ -54,7 +59,12 @@ class KeyboardControllerViewManager(
|
|
|
54
59
|
) {
|
|
55
60
|
manager.setPreserveEdgeToEdge(view, isPreservingEdgeToEdge)
|
|
56
61
|
}
|
|
62
|
+
// endregion
|
|
57
63
|
|
|
64
|
+
// region Getters
|
|
58
65
|
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
|
|
59
66
|
manager.getExportedCustomDirectEventTypeConstants()
|
|
67
|
+
|
|
68
|
+
override fun getName(): String = KeyboardControllerViewManagerImpl.NAME
|
|
69
|
+
// endregion
|
|
60
70
|
}
|
|
@@ -8,29 +8,64 @@
|
|
|
8
8
|
import Foundation
|
|
9
9
|
import UIKit
|
|
10
10
|
|
|
11
|
+
private var pendingBecomeResponder: TextInput?
|
|
11
12
|
private var originalResignFirstResponder: IMP?
|
|
13
|
+
private var originalBecomeFirstResponder: IMP?
|
|
12
14
|
|
|
13
15
|
@objc
|
|
14
16
|
extension UIResponder {
|
|
17
|
+
public static func swizzleBecomeFirstResponder() {
|
|
18
|
+
let originalBecomeSelector = #selector(becomeFirstResponder)
|
|
19
|
+
guard
|
|
20
|
+
let originalBecomeMethod = class_getInstanceMethod(UIResponder.self, originalBecomeSelector)
|
|
21
|
+
else {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
originalBecomeFirstResponder = method_getImplementation(originalBecomeMethod)
|
|
26
|
+
|
|
27
|
+
let swizzledBecomeImplementation: @convention(block) (UIResponder) -> Bool = { (self) in
|
|
28
|
+
pendingBecomeResponder = self as? TextInput
|
|
29
|
+
|
|
30
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + UIUtils.nextFrame) {
|
|
31
|
+
pendingBecomeResponder = nil
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return self.callOriginalBecomeFirstResponder(originalBecomeSelector)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let implementation = imp_implementationWithBlock(swizzledBecomeImplementation)
|
|
38
|
+
method_setImplementation(originalBecomeMethod, implementation)
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
public static func swizzleResignFirstResponder() {
|
|
16
|
-
let
|
|
42
|
+
let originalResignSelector = #selector(resignFirstResponder)
|
|
17
43
|
|
|
18
|
-
guard
|
|
44
|
+
guard
|
|
45
|
+
let originalResignMethod = class_getInstanceMethod(UIResponder.self, originalResignSelector)
|
|
46
|
+
else {
|
|
19
47
|
return
|
|
20
48
|
}
|
|
21
49
|
|
|
22
|
-
originalResignFirstResponder = method_getImplementation(
|
|
50
|
+
originalResignFirstResponder = method_getImplementation(originalResignMethod)
|
|
23
51
|
|
|
24
|
-
let
|
|
52
|
+
let swizzledResignImplementation: @convention(block) (UIResponder) -> Bool = { (self) in
|
|
25
53
|
// Check the type of responder
|
|
26
54
|
if let textField = self as? TextInput {
|
|
27
55
|
// check inputAccessoryView and call original method immediately if not InvisibleInputAccessoryView
|
|
28
56
|
if !(textField.inputAccessoryView is InvisibleInputAccessoryView) {
|
|
29
|
-
return self.callOriginalResignFirstResponder(
|
|
57
|
+
return self.callOriginalResignFirstResponder(originalResignSelector)
|
|
58
|
+
} else if pendingBecomeResponder != nil {
|
|
59
|
+
// if we already have a new focus request
|
|
60
|
+
pendingBecomeResponder = nil
|
|
61
|
+
KeyboardAreaExtender.shared.hide()
|
|
62
|
+
(self as? TextInput)?.inputAccessoryView = nil
|
|
63
|
+
KeyboardAreaExtender.shared.remove()
|
|
64
|
+
return self.callOriginalResignFirstResponder(originalResignSelector)
|
|
30
65
|
}
|
|
31
66
|
} else {
|
|
32
67
|
// If casting to TextInput fails
|
|
33
|
-
return self.callOriginalResignFirstResponder(
|
|
68
|
+
return self.callOriginalResignFirstResponder(originalResignSelector)
|
|
34
69
|
}
|
|
35
70
|
|
|
36
71
|
KeyboardAreaExtender.shared.hide()
|
|
@@ -39,22 +74,34 @@ extension UIResponder {
|
|
|
39
74
|
DispatchQueue.main.asyncAfter(deadline: .now() + UIUtils.nextFrame) {
|
|
40
75
|
(self as? TextInput)?.inputAccessoryView = nil
|
|
41
76
|
KeyboardAreaExtender.shared.remove()
|
|
42
|
-
_ = self.callOriginalResignFirstResponder(
|
|
77
|
+
_ = self.callOriginalResignFirstResponder(originalResignSelector)
|
|
43
78
|
}
|
|
44
79
|
|
|
45
80
|
// We need to return a value immediately, even though the actual action is delayed
|
|
46
81
|
return false
|
|
47
82
|
}
|
|
48
83
|
|
|
49
|
-
let implementation = imp_implementationWithBlock(
|
|
50
|
-
method_setImplementation(
|
|
84
|
+
let implementation = imp_implementationWithBlock(swizzledResignImplementation)
|
|
85
|
+
method_setImplementation(originalResignMethod, implementation)
|
|
51
86
|
}
|
|
52
87
|
|
|
53
88
|
private func callOriginalResignFirstResponder(_ selector: Selector) -> Bool {
|
|
54
89
|
guard let originalResignFirstResponder = originalResignFirstResponder else { return false }
|
|
55
90
|
typealias Function = @convention(c) (AnyObject, Selector) -> Bool
|
|
56
|
-
let castOriginalResignFirstResponder = unsafeBitCast(
|
|
91
|
+
let castOriginalResignFirstResponder = unsafeBitCast(
|
|
92
|
+
originalResignFirstResponder, to: Function.self
|
|
93
|
+
)
|
|
57
94
|
let result = castOriginalResignFirstResponder(self, selector)
|
|
58
95
|
return result
|
|
59
96
|
}
|
|
97
|
+
|
|
98
|
+
private func callOriginalBecomeFirstResponder(_ selector: Selector) -> Bool {
|
|
99
|
+
guard let originalBecomeFirstResponder = originalBecomeFirstResponder else { return false }
|
|
100
|
+
typealias Function = @convention(c) (AnyObject, Selector) -> Bool
|
|
101
|
+
let castOriginalBecomeFirstResponder = unsafeBitCast(
|
|
102
|
+
originalBecomeFirstResponder, to: Function.self
|
|
103
|
+
)
|
|
104
|
+
let result = castOriginalBecomeFirstResponder(self, selector)
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
60
107
|
}
|
package/package.json
CHANGED