react-native-keyboard-controller 1.21.0-beta.2 → 1.21.0-beta.3
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/ClippingScrollViewDecoratorViewManager.kt +7 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +28 -1
- package/android/src/paper/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +8 -0
- package/ios/animations/KeyboardAnimation.swift +2 -2
- package/ios/animations/SpringAnimation.swift +1 -1
- package/ios/animations/TimingAnimation.swift +1 -3
- package/ios/delegates/KCTextInputCompositeDelegate.swift +2 -2
- package/ios/extensions/CGFloat.swift +1 -3
- package/ios/interactive/KeyboardAreaExtender.swift +2 -1
- package/ios/observers/FocusedInputObserver.swift +4 -2
- package/ios/observers/movement/KeyboardTrackingView.swift +4 -1
- package/ios/observers/movement/observer/KeyboardMovementObserver+Interactive.swift +2 -0
- package/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift +30 -7
- package/ios/observers/movement/observer/KeyboardMovementObserver.swift +2 -48
- package/ios/protocols/TextInput.swift +1 -1
- package/ios/swizzling/UIResponderSwizzle.swift +2 -4
- package/ios/traversal/FocusedInputHolder.swift +3 -3
- package/ios/traversal/KeyboardView.swift +1 -1
- package/ios/traversal/ViewHierarchyNavigator.swift +2 -2
- package/ios/views/KeyboardControllerViewManager.swift +5 -5
- package/jest/index.js +1 -3
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +43 -11
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/hooks.js.map +1 -1
- package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/index.js +37 -12
- package/lib/commonjs/components/KeyboardChatScrollView/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/types.js.map +1 -0
- package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.js +35 -1
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +121 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js +205 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js +6 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -0
- package/lib/commonjs/components/KeyboardStickyView/index.js +3 -1
- package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js +18 -9
- package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js.map +1 -1
- package/lib/commonjs/components/hooks/useScrollState.js +20 -1
- package/lib/commonjs/components/hooks/useScrollState.js.map +1 -1
- package/lib/commonjs/components/index.js +7 -7
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/index.js +7 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
- package/lib/commonjs/types/views.js.map +1 -1
- package/lib/module/components/KeyboardAwareScrollView/index.js +43 -11
- package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/module/components/{ChatKit → KeyboardChatScrollView}/hooks.js.map +1 -1
- package/lib/module/components/KeyboardChatScrollView/index.js +73 -0
- package/lib/module/components/KeyboardChatScrollView/index.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/types.js.map +1 -0
- package/lib/module/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.js +34 -1
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +115 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js +199 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js +2 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -0
- package/lib/module/components/KeyboardStickyView/index.js +3 -1
- package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/module/components/ScrollViewWithBottomPadding/index.js +19 -10
- package/lib/module/components/ScrollViewWithBottomPadding/index.js.map +1 -1
- package/lib/module/components/hooks/useScrollState.js +21 -2
- package/lib/module/components/hooks/useScrollState.js.map +1 -1
- package/lib/module/components/index.js +1 -1
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
- package/lib/module/types/views.js.map +1 -1
- package/lib/typescript/components/KeyboardChatScrollView/hooks.d.ts +2 -0
- package/lib/typescript/components/KeyboardChatScrollView/index.d.ts +12 -0
- package/lib/typescript/components/{ChatKit → KeyboardChatScrollView}/types.d.ts +5 -6
- package/lib/typescript/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.d.ts +23 -3
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.d.ts +21 -0
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.ios.d.ts +21 -0
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/types.d.ts +22 -0
- package/lib/typescript/components/ScrollViewWithBottomPadding/index.d.ts +2 -4
- package/lib/typescript/components/hooks/useScrollState.d.ts +3 -0
- package/lib/typescript/components/index.d.ts +2 -2
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/specs/ClippingScrollViewDecoratorViewNativeComponent.d.ts +1 -0
- package/lib/typescript/types/views.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/KeyboardAwareScrollView/index.tsx +53 -10
- package/src/components/KeyboardChatScrollView/hooks.ts +2 -0
- package/src/components/KeyboardChatScrollView/index.tsx +106 -0
- package/src/components/{ChatKit → KeyboardChatScrollView}/types.ts +5 -6
- package/src/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.ts +46 -1
- package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ios.ts +180 -0
- package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts +300 -0
- package/src/components/KeyboardChatScrollView/useChatKeyboard/types.ts +30 -0
- package/src/components/KeyboardStickyView/index.tsx +4 -3
- package/src/components/ScrollViewWithBottomPadding/index.tsx +22 -25
- package/src/components/hooks/useScrollState.ts +24 -2
- package/src/components/index.ts +2 -2
- package/src/index.ts +2 -2
- package/src/specs/ClippingScrollViewDecoratorViewNativeComponent.ts +1 -0
- package/src/types/views.ts +2 -0
- package/lib/commonjs/components/ChatKit/TODO.md +0 -20
- package/lib/commonjs/components/ChatKit/index.js.map +0 -1
- package/lib/commonjs/components/ChatKit/types.js.map +0 -1
- package/lib/commonjs/components/ChatKit/useChatKeyboard/helpers.js.map +0 -1
- package/lib/commonjs/components/ChatKit/useChatKeyboard/index.js +0 -147
- package/lib/commonjs/components/ChatKit/useChatKeyboard/index.js.map +0 -1
- package/lib/module/components/ChatKit/TODO.md +0 -20
- package/lib/module/components/ChatKit/index.js +0 -48
- package/lib/module/components/ChatKit/index.js.map +0 -1
- package/lib/module/components/ChatKit/types.js.map +0 -1
- package/lib/module/components/ChatKit/useChatKeyboard/helpers.js.map +0 -1
- package/lib/module/components/ChatKit/useChatKeyboard/index.js +0 -141
- package/lib/module/components/ChatKit/useChatKeyboard/index.js.map +0 -1
- package/lib/typescript/components/ChatKit/hooks.d.ts +0 -2
- package/lib/typescript/components/ChatKit/index.d.ts +0 -14
- package/lib/typescript/components/ChatKit/useChatKeyboard/index.d.ts +0 -36
- package/src/components/ChatKit/TODO.md +0 -20
- package/src/components/ChatKit/hooks.ts +0 -2
- package/src/components/ChatKit/index.tsx +0 -63
- package/src/components/ChatKit/useChatKeyboard/index.ts +0 -228
- /package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/hooks.js +0 -0
- /package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/types.js +0 -0
- /package/lib/module/components/{ChatKit → KeyboardChatScrollView}/hooks.js +0 -0
- /package/lib/module/components/{ChatKit → KeyboardChatScrollView}/types.js +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.reactnativekeyboardcontroller.views
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
+
import android.view.ViewGroup
|
|
4
5
|
import android.widget.ScrollView
|
|
5
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
7
|
import com.facebook.react.views.view.ReactViewGroup
|
|
@@ -11,6 +12,8 @@ class ClippingScrollViewDecoratorView(
|
|
|
11
12
|
val reactContext: ThemedReactContext,
|
|
12
13
|
) : ReactViewGroup(reactContext) {
|
|
13
14
|
private var insetBottom = 0.0
|
|
15
|
+
private var insetTop = 0.0
|
|
16
|
+
private var appliedTopInsetPx = 0
|
|
14
17
|
|
|
15
18
|
override fun onAttachedToWindow() {
|
|
16
19
|
super.onAttachedToWindow()
|
|
@@ -23,15 +26,39 @@ class ClippingScrollViewDecoratorView(
|
|
|
23
26
|
decorateScrollView()
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
fun setContentInsetTop(value: Double) {
|
|
30
|
+
insetTop = value
|
|
31
|
+
decorateScrollView()
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
private fun decorateScrollView() {
|
|
27
35
|
val scrollView = getChildAt(0) as? ScrollView ?: return
|
|
28
36
|
|
|
29
37
|
scrollView.clipToPadding = false
|
|
38
|
+
|
|
39
|
+
val newTopInsetPx = insetTop.toFloat().px.toInt()
|
|
40
|
+
|
|
41
|
+
// Translate the content view as a whole — this keeps FlatList's
|
|
42
|
+
// virtualizer calculations correct (it reads layout positions,
|
|
43
|
+
// not translationY).
|
|
44
|
+
val contentView = scrollView.getChildAt(0) as? ViewGroup ?: return
|
|
45
|
+
(contentView.getChildAt(0) as? ViewGroup)?.translationY = newTopInsetPx.toFloat()
|
|
46
|
+
|
|
30
47
|
scrollView.setPadding(
|
|
31
48
|
scrollView.paddingLeft,
|
|
32
49
|
scrollView.paddingTop,
|
|
33
50
|
scrollView.paddingRight,
|
|
34
|
-
|
|
51
|
+
// pass accumulated value — visually both top and bottom insets
|
|
52
|
+
// extend the scroll range via bottom padding
|
|
53
|
+
(insetBottom + insetTop).toFloat().px.toInt(),
|
|
35
54
|
)
|
|
55
|
+
|
|
56
|
+
// scroll by the delta to keep content visually in place
|
|
57
|
+
val delta = newTopInsetPx - appliedTopInsetPx
|
|
58
|
+
if (delta != 0) {
|
|
59
|
+
scrollView.scrollBy(0, delta)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
appliedTopInsetPx = newTopInsetPx
|
|
36
63
|
}
|
|
37
64
|
}
|
|
@@ -21,4 +21,12 @@ class ClippingScrollViewDecoratorViewManager : ViewGroupManager<ClippingScrollVi
|
|
|
21
21
|
) {
|
|
22
22
|
view.setContentInsetBottom(value)
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
@ReactProp(name = "contentInsetTop")
|
|
26
|
+
fun setContentInsetTop(
|
|
27
|
+
view: ClippingScrollViewDecoratorView,
|
|
28
|
+
value: Double,
|
|
29
|
+
) {
|
|
30
|
+
view.setContentInsetTop(value)
|
|
31
|
+
}
|
|
24
32
|
}
|
|
@@ -26,7 +26,7 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
|
|
|
26
26
|
let duration: Double
|
|
27
27
|
let speed: Double
|
|
28
28
|
let timestamp: CFTimeInterval
|
|
29
|
-
|
|
29
|
+
/// internal state
|
|
30
30
|
var lastValue: Double
|
|
31
31
|
|
|
32
32
|
init(fromValue: Double, toValue: Double, animation: CAMediaTiming, duration: Double) {
|
|
@@ -39,7 +39,7 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
|
|
|
39
39
|
timestamp = CACurrentMediaTime()
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
/// public getters
|
|
43
43
|
var startTime: CFTimeInterval {
|
|
44
44
|
// when concurrent animation happens, then `.beginTime` remains the same
|
|
45
45
|
return max(animation?.beginTime ?? timestamp, timestamp)
|
|
@@ -60,7 +60,7 @@ public final class SpringAnimation: KeyboardAnimation {
|
|
|
60
60
|
super.init(fromValue: fromValue, toValue: toValue, animation: animation, duration: animation.settlingDuration)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
/// public functions
|
|
64
64
|
override func valueAt(time: Double) -> Double {
|
|
65
65
|
let t = time * speed
|
|
66
66
|
|
|
@@ -51,7 +51,7 @@ class KCTextInputCompositeDelegate: NSObject, UITextViewDelegate, UITextFieldDel
|
|
|
51
51
|
weak var textViewDelegate: UITextViewDelegate?
|
|
52
52
|
weak var textFieldDelegate: UITextFieldDelegate?
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
/// Keep track of which textField we’re observing (iOS < 13 only)
|
|
55
55
|
private weak var observedTextFieldForSelection: UITextField?
|
|
56
56
|
|
|
57
57
|
init(
|
|
@@ -111,7 +111,7 @@ class KCTextInputCompositeDelegate: NSObject, UITextViewDelegate, UITextFieldDel
|
|
|
111
111
|
return true
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
/// Getter for the active delegate
|
|
115
115
|
var activeDelegate: AnyObject? {
|
|
116
116
|
return textViewDelegate ?? textFieldDelegate
|
|
117
117
|
}
|
|
@@ -16,8 +16,6 @@ public extension CGFloat {
|
|
|
16
16
|
let outputMax = outputRange.max() ?? 1
|
|
17
17
|
|
|
18
18
|
let normalizedValue = (currentValue - inputMin) / (inputMax - inputMin)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return interpolatedValue
|
|
19
|
+
return outputMin + (outputMax - outputMin) * normalizedValue
|
|
22
20
|
}
|
|
23
21
|
}
|
|
@@ -55,7 +55,8 @@ class KeyboardAreaExtender: NSObject {
|
|
|
55
55
|
let responder = UIResponder.current
|
|
56
56
|
if let activeTextInput = responder as? TextInput,
|
|
57
57
|
let offset = KeyboardOffsetProvider.shared.getOffset(
|
|
58
|
-
forTextInputNativeID: responder.nativeID
|
|
58
|
+
forTextInputNativeID: responder.nativeID
|
|
59
|
+
),
|
|
59
60
|
responder?.inputAccessoryView == nil
|
|
60
61
|
{
|
|
61
62
|
currentInputAccessoryView = InvisibleInputAccessoryView(height: CGFloat(truncating: offset))
|
|
@@ -32,7 +32,7 @@ public class FocusedInputObserver: NSObject {
|
|
|
32
32
|
var onFocusDidSet: (NSDictionary) -> Void
|
|
33
33
|
/// delegates
|
|
34
34
|
var delegate: KCTextInputCompositeDelegate
|
|
35
|
-
|
|
35
|
+
/// state variables
|
|
36
36
|
private var isMounted = false
|
|
37
37
|
// input tracking
|
|
38
38
|
private var currentInput: UIView?
|
|
@@ -142,7 +142,9 @@ public class FocusedInputObserver: NSObject {
|
|
|
142
142
|
setupObservers()
|
|
143
143
|
// dispatch onSelectionChange on focus
|
|
144
144
|
if let textInput = responder as? UITextInput {
|
|
145
|
-
|
|
145
|
+
DispatchQueue.main.async {
|
|
146
|
+
updateSelectionPosition(textInput: textInput, sendEvent: self.onSelectionChange)
|
|
147
|
+
}
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
syncUpLayout()
|
|
@@ -12,7 +12,10 @@ import UIKit
|
|
|
12
12
|
* and uses `keyboardLayoutGuide` on iOS 26+.
|
|
13
13
|
*/
|
|
14
14
|
public final class KeyboardTrackingView: UIView {
|
|
15
|
-
private var keyboardView: UIView? {
|
|
15
|
+
private var keyboardView: UIView? {
|
|
16
|
+
KeyboardViewLocator.shared.resolve()
|
|
17
|
+
}
|
|
18
|
+
|
|
16
19
|
private var keyboardHeight = 0.0
|
|
17
20
|
private weak var currentAttachedView: UIView?
|
|
18
21
|
private var isAttaching = false
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
extension KeyboardMovementObserver {
|
|
9
9
|
@objc func keyboardWillAppear(_ notification: Notification) {
|
|
10
|
-
guard !
|
|
10
|
+
guard !UIResponder.isKeyboardPreloading else { return }
|
|
11
11
|
|
|
12
12
|
let (duration, frame) = notification.keyboardMetaData()
|
|
13
13
|
if let keyboardFrame = frame {
|
|
@@ -15,6 +15,12 @@ extension KeyboardMovementObserver {
|
|
|
15
15
|
let keyboardHeight = keyboardFrame.cgRectValue.size.height
|
|
16
16
|
self.keyboardHeight = keyboardHeight
|
|
17
17
|
self.notification = notification
|
|
18
|
+
|
|
19
|
+
guard !KeyboardEventsIgnorer.shared.shouldIgnore else {
|
|
20
|
+
KeyboardEventsIgnorer.shared.shouldIgnoreKeyboardEvents = false
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
self.duration = duration
|
|
19
25
|
|
|
20
26
|
onRequestAnimation()
|
|
@@ -23,6 +29,7 @@ extension KeyboardMovementObserver {
|
|
|
23
29
|
|
|
24
30
|
setupKeyboardWatcher()
|
|
25
31
|
initializeAnimation(fromValue: prevKeyboardPosition, toValue: self.keyboardHeight)
|
|
32
|
+
scheduleDidEvent(height: self.keyboardHeight, duration: animation?.duration ?? CGFloat(duration) / 1000)
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
35
|
|
|
@@ -44,6 +51,7 @@ extension KeyboardMovementObserver {
|
|
|
44
51
|
setupKeyboardWatcher()
|
|
45
52
|
removeKVObserver()
|
|
46
53
|
initializeAnimation(fromValue: prevKeyboardPosition, toValue: 0)
|
|
54
|
+
scheduleDidEvent(height: 0, duration: animation?.duration ?? CGFloat(duration) / 1000)
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
@objc func keyboardDidAppear(_ notification: Notification) {
|
|
@@ -55,12 +63,7 @@ extension KeyboardMovementObserver {
|
|
|
55
63
|
tag = UIResponder.current.reactViewTag
|
|
56
64
|
self.keyboardHeight = keyboardHeight
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
KeyboardEventsIgnorer.shared.shouldIgnoreKeyboardEvents = false
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
let height = self.keyboardHeight - KeyboardAreaExtender.shared.offset
|
|
66
|
+
let height = self.keyboardHeight
|
|
64
67
|
// always limit progress to the maximum possible value
|
|
65
68
|
let progress = min(height / self.keyboardHeight, 1.0)
|
|
66
69
|
|
|
@@ -90,4 +93,24 @@ extension KeyboardMovementObserver {
|
|
|
90
93
|
removeKeyboardWatcher()
|
|
91
94
|
animation = nil
|
|
92
95
|
}
|
|
96
|
+
|
|
97
|
+
@objc func scheduleDidEvent(height: CGFloat, duration: CGFloat) {
|
|
98
|
+
keyboardDidTask?.cancel()
|
|
99
|
+
|
|
100
|
+
guard let notification = notification else {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let task = DispatchWorkItem { [weak self] in
|
|
105
|
+
guard let self = self else { return }
|
|
106
|
+
if height > 0 {
|
|
107
|
+
self.keyboardDidAppear(notification)
|
|
108
|
+
} else {
|
|
109
|
+
self.keyboardDidDisappear(notification)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
keyboardDidTask = task
|
|
114
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: task)
|
|
115
|
+
}
|
|
93
116
|
}
|
|
@@ -19,30 +19,7 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
19
19
|
var onCancelAnimation: () -> Void
|
|
20
20
|
// progress tracker
|
|
21
21
|
@objc public var keyboardTrackingView = KeyboardTrackingView()
|
|
22
|
-
var animation: KeyboardAnimation?
|
|
23
|
-
didSet {
|
|
24
|
-
keyboardDidTask?.cancel()
|
|
25
|
-
|
|
26
|
-
guard let animation = animation, let notification = notification else {
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let toValue = animation.toValue
|
|
31
|
-
let duration = animation.duration
|
|
32
|
-
|
|
33
|
-
let task = DispatchWorkItem { [weak self] in
|
|
34
|
-
guard let self = self else { return }
|
|
35
|
-
if toValue > 0 {
|
|
36
|
-
self.keyboardDidAppear(notification)
|
|
37
|
-
} else {
|
|
38
|
-
self.keyboardDidDisappear(notification)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
keyboardDidTask = task
|
|
43
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: task)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
22
|
+
var animation: KeyboardAnimation?
|
|
46
23
|
|
|
47
24
|
var prevKeyboardPosition = 0.0
|
|
48
25
|
var displayLink: CADisplayLink!
|
|
@@ -55,30 +32,7 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
55
32
|
set { _keyboardHeight = newValue }
|
|
56
33
|
}
|
|
57
34
|
|
|
58
|
-
var duration = 0
|
|
59
|
-
didSet {
|
|
60
|
-
keyboardDidTask?.cancel()
|
|
61
|
-
|
|
62
|
-
guard let notification = notification,
|
|
63
|
-
let height = notification.keyboardMetaData().1?.cgRectValue.size.height, duration == 0
|
|
64
|
-
else {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let task = DispatchWorkItem { [weak self] in
|
|
69
|
-
guard let self = self else { return }
|
|
70
|
-
if height > 0 {
|
|
71
|
-
self.keyboardDidAppear(notification)
|
|
72
|
-
} else {
|
|
73
|
-
self.keyboardDidDisappear(notification)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
keyboardDidTask = task
|
|
78
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + UIUtils.nextFrame, execute: task)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
35
|
+
var duration = 0
|
|
82
36
|
var tag: NSNumber = -1
|
|
83
37
|
// manual did events
|
|
84
38
|
var notification: Notification?
|
|
@@ -15,7 +15,7 @@ public protocol TextInput: UIView {
|
|
|
15
15
|
var inputView: UIView? { get set }
|
|
16
16
|
var keyboardType: UIKeyboardType { get }
|
|
17
17
|
var keyboardAppearance: UIKeyboardAppearance { get }
|
|
18
|
-
|
|
18
|
+
/// custom methods/properties
|
|
19
19
|
func focus()
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -94,8 +94,7 @@ extension UIResponder {
|
|
|
94
94
|
let castOriginalResignFirstResponder = unsafeBitCast(
|
|
95
95
|
originalResignFirstResponder, to: Function.self
|
|
96
96
|
)
|
|
97
|
-
|
|
98
|
-
return result
|
|
97
|
+
return castOriginalResignFirstResponder(self, selector)
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
private func callOriginalBecomeFirstResponder(_ selector: Selector) -> Bool {
|
|
@@ -104,7 +103,6 @@ extension UIResponder {
|
|
|
104
103
|
let castOriginalBecomeFirstResponder = unsafeBitCast(
|
|
105
104
|
originalBecomeFirstResponder, to: Function.self
|
|
106
105
|
)
|
|
107
|
-
|
|
108
|
-
return result
|
|
106
|
+
return castOriginalBecomeFirstResponder(self, selector)
|
|
109
107
|
}
|
|
110
108
|
}
|
|
@@ -11,10 +11,10 @@ import Foundation
|
|
|
11
11
|
class FocusedInputHolder {
|
|
12
12
|
static let shared = FocusedInputHolder()
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/// Using TextInput protocol as the type for currentFocusedInput
|
|
15
15
|
private weak var currentFocusedInput: TextInput?
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/// Sets the currentFocusedInput to the passed instance conforming to TextInput or clears it if nil is passed
|
|
18
18
|
func set(_ input: TextInput?) {
|
|
19
19
|
currentFocusedInput = input
|
|
20
20
|
}
|
|
@@ -23,7 +23,7 @@ class FocusedInputHolder {
|
|
|
23
23
|
return currentFocusedInput
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/// Requests focus for the currentFocusedInput if it's set
|
|
27
27
|
func focus() {
|
|
28
28
|
currentFocusedInput?.focus()
|
|
29
29
|
}
|
|
@@ -13,7 +13,7 @@ enum KeyboardView {
|
|
|
13
13
|
private static let windowPrefix = "<UITextEffectsWindow"
|
|
14
14
|
private static let containerPrefixes = ["<UIInputSetContainerView", "<UITrackingWindowView"]
|
|
15
15
|
private static let hostPrefixes = ["<UIInputSetHostView", "<UIKeyboardItemContainerView"]
|
|
16
|
-
|
|
16
|
+
/// inspired by https://stackoverflow.com/questions/32598490/show-uiview-with-buttons-over-keyboard-like-in-skype-viber-messengers-swift-i
|
|
17
17
|
static func find() -> UIView? {
|
|
18
18
|
let windows = UIApplication.shared.windows
|
|
19
19
|
for window in windows {
|
|
@@ -36,7 +36,7 @@ public class ViewHierarchyNavigator: NSObject {
|
|
|
36
36
|
return []
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
/// Helper function to recursively search for TextInput views
|
|
40
40
|
func findTextInputs(in view: UIView?) {
|
|
41
41
|
guard let view = view else { return }
|
|
42
42
|
|
|
@@ -104,7 +104,7 @@ public class ViewHierarchyNavigator: NSObject {
|
|
|
104
104
|
return nil // No valid UITextField or UITextView found
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
/// Function to check if the view is a valid text field or text view
|
|
108
108
|
private static func isValidTextInput(_ view: UIView) -> TextInput? {
|
|
109
109
|
if let textField = view as? UITextField, textField.isEnabled {
|
|
110
110
|
return textField
|
|
@@ -27,7 +27,7 @@ class KeyboardControllerView: UIView {
|
|
|
27
27
|
var inputObserver: FocusedInputObserver?
|
|
28
28
|
private var eventDispatcher: RCTEventDispatcherProtocol
|
|
29
29
|
private var bridge: RCTBridge
|
|
30
|
-
|
|
30
|
+
/// internal state
|
|
31
31
|
private var lastScreenSize: CGSize = .zero
|
|
32
32
|
// react callbacks
|
|
33
33
|
/// keyboard
|
|
@@ -39,7 +39,7 @@ class KeyboardControllerView: UIView {
|
|
|
39
39
|
@objc var onFocusedInputLayoutChanged: RCTDirectEventBlock?
|
|
40
40
|
@objc var onFocusedInputTextChanged: RCTDirectEventBlock?
|
|
41
41
|
@objc var onFocusedInputSelectionChanged: RCTDirectEventBlock?
|
|
42
|
-
|
|
42
|
+
/// react props
|
|
43
43
|
@objc var enabled: ObjCBool = true {
|
|
44
44
|
didSet {
|
|
45
45
|
if enabled.boolValue {
|
|
@@ -83,9 +83,9 @@ class KeyboardControllerView: UIView {
|
|
|
83
83
|
fatalError("init(coder:) has not been implemented")
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
/// for mounting/unmounting observers for lifecycle events we're using willMove(toSuperview) method
|
|
87
|
+
/// not willMove(toWindow)
|
|
88
|
+
/// see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/271
|
|
89
89
|
override func willMove(toSuperview newSuperview: UIView?) {
|
|
90
90
|
super.willMove(toSuperview: newSuperview)
|
|
91
91
|
|
package/jest/index.js
CHANGED
|
@@ -120,9 +120,7 @@ const mock = {
|
|
|
120
120
|
KeyboardAvoidingView: View,
|
|
121
121
|
KeyboardAwareScrollView: ScrollView,
|
|
122
122
|
KeyboardToolbar: View,
|
|
123
|
-
|
|
124
|
-
ScrollView: ScrollView,
|
|
125
|
-
},
|
|
123
|
+
KeyboardChatScrollView: ScrollView,
|
|
126
124
|
// themes
|
|
127
125
|
DefaultKeyboardToolbarTheme,
|
|
128
126
|
};
|
|
@@ -103,6 +103,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
|
103
103
|
const layout = (0, _reactNativeReanimated.useSharedValue)(null);
|
|
104
104
|
const lastSelection = (0, _reactNativeReanimated.useSharedValue)(null);
|
|
105
105
|
const ghostViewSpace = (0, _reactNativeReanimated.useSharedValue)(-1);
|
|
106
|
+
const pendingSelectionForFocus = (0, _reactNativeReanimated.useSharedValue)(false);
|
|
106
107
|
const {
|
|
107
108
|
height
|
|
108
109
|
} = (0, _hooks.useWindowDimensions)();
|
|
@@ -215,7 +216,17 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
|
215
216
|
const latestSelection = (_lastSelection$value3 = lastSelection.value) === null || _lastSelection$value3 === void 0 ? void 0 : _lastSelection$value3.selection;
|
|
216
217
|
lastSelection.value = e;
|
|
217
218
|
if (e.target !== lastTarget) {
|
|
218
|
-
|
|
219
|
+
if (pendingSelectionForFocus.value) {
|
|
220
|
+
// selection arrived after onStart - complete the deferred setup
|
|
221
|
+
pendingSelectionForFocus.value = false;
|
|
222
|
+
updateLayoutFromSelection();
|
|
223
|
+
|
|
224
|
+
// if keyboard was already visible (focus change, no onMove expected),
|
|
225
|
+
// perform the deferred scroll now
|
|
226
|
+
if (!keyboardWillAppear.value && keyboardHeight.value > 0) {
|
|
227
|
+
position.value += maybeScroll(keyboardHeight.value, true);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
219
230
|
return;
|
|
220
231
|
}
|
|
221
232
|
// caret in the end + end coordinates has been changed -> we moved to a new line
|
|
@@ -228,7 +239,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
|
228
239
|
return scrollFromCurrentPosition();
|
|
229
240
|
}
|
|
230
241
|
onChangeTextHandler();
|
|
231
|
-
}, [scrollFromCurrentPosition, onChangeTextHandler]);
|
|
242
|
+
}, [scrollFromCurrentPosition, onChangeTextHandler, updateLayoutFromSelection, maybeScroll]);
|
|
232
243
|
(0, _hooks.useFocusedInputHandler)({
|
|
233
244
|
onSelectionChange: onSelectionChange
|
|
234
245
|
}, [onSelectionChange]);
|
|
@@ -247,6 +258,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
|
247
258
|
// on back transition need to interpolate as [0, keyboardHeight]
|
|
248
259
|
initialKeyboardSize.value = 0;
|
|
249
260
|
scrollPosition.value = scrollBeforeKeyboardMovement.value;
|
|
261
|
+
pendingSelectionForFocus.value = false;
|
|
250
262
|
}
|
|
251
263
|
if (keyboardWillAppear.value || keyboardWillChangeSize || focusWasChanged) {
|
|
252
264
|
// persist scroll value
|
|
@@ -257,17 +269,31 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
|
257
269
|
|
|
258
270
|
// focus was changed
|
|
259
271
|
if (focusWasChanged) {
|
|
272
|
+
var _lastSelection$value4;
|
|
260
273
|
tag.value = e.target;
|
|
261
|
-
|
|
262
|
-
|
|
274
|
+
if (((_lastSelection$value4 = lastSelection.value) === null || _lastSelection$value4 === void 0 ? void 0 : _lastSelection$value4.target) === e.target) {
|
|
275
|
+
// selection arrived before onStart - use it to update layout
|
|
276
|
+
updateLayoutFromSelection();
|
|
277
|
+
pendingSelectionForFocus.value = false;
|
|
278
|
+
} else {
|
|
279
|
+
// selection hasn't arrived yet for the new target.
|
|
280
|
+
// use input layout as-is; will be refined when selection arrives.
|
|
281
|
+
if (input.value) {
|
|
282
|
+
layout.value = input.value;
|
|
283
|
+
}
|
|
284
|
+
pendingSelectionForFocus.value = true;
|
|
285
|
+
}
|
|
286
|
+
|
|
263
287
|
// save current scroll position - when keyboard will hide we'll reuse
|
|
264
288
|
// this value to achieve smooth hide effect
|
|
265
289
|
scrollBeforeKeyboardMovement.value = position.value;
|
|
266
290
|
}
|
|
267
291
|
if (focusWasChanged && !keyboardWillAppear.value) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
292
|
+
if (!pendingSelectionForFocus.value) {
|
|
293
|
+
// update position on scroll value, so `onEnd` handler
|
|
294
|
+
// will pick up correct values
|
|
295
|
+
position.value += maybeScroll(e.height, true);
|
|
296
|
+
}
|
|
271
297
|
}
|
|
272
298
|
ghostViewSpace.value = position.value + scrollViewLayout.value.height - scrollViewContentSize.value.height;
|
|
273
299
|
if (ghostViewSpace.value > 0) {
|
|
@@ -293,6 +319,9 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
|
293
319
|
removeGhostPadding(e.height);
|
|
294
320
|
keyboardHeight.value = e.height;
|
|
295
321
|
scrollPosition.value = position.value;
|
|
322
|
+
if (e.height === 0) {
|
|
323
|
+
lastSelection.value = null;
|
|
324
|
+
}
|
|
296
325
|
syncKeyboardFrame(e);
|
|
297
326
|
}
|
|
298
327
|
}, [maybeScroll, removeGhostPadding, disableScrollOnKeyboardHide, syncKeyboardFrame]);
|
|
@@ -306,11 +335,14 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
|
|
|
306
335
|
}, [update, scrollFromCurrentPosition]);
|
|
307
336
|
(0, _react.useImperativeHandle)(ref, () => {
|
|
308
337
|
const scrollView = scrollViewRef.current;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
338
|
+
if (scrollView) {
|
|
339
|
+
const scrollViewWithMethods = scrollView;
|
|
340
|
+
scrollViewWithMethods.assureFocusedInputVisible = () => {
|
|
341
|
+
synchronize();
|
|
342
|
+
};
|
|
343
|
+
return scrollViewWithMethods;
|
|
344
|
+
}
|
|
312
345
|
return {
|
|
313
|
-
...existingMethods,
|
|
314
346
|
assureFocusedInputVisible: () => {
|
|
315
347
|
synchronize();
|
|
316
348
|
}
|