react-native-keyboard-controller 1.14.4 → 1.15.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/android/build.gradle +3 -2
- package/android/gradle.properties +1 -0
- package/android/react-native-helpers.gradle +5 -11
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +2 -2
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +35 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +18 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +5 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/modal/ModalAttachedWatcher.kt +9 -6
- package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +13 -4
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/overlay/JSPointerDispatcherCompat.kt +56 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/overlay/OverKeyboardViewGroup.kt +10 -7
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +2 -2
- package/ios/KeyboardControllerModule.mm +3 -3
- package/ios/KeyboardControllerModuleImpl.swift +66 -0
- package/ios/events/KeyboardEventEmitterPayload.swift +23 -0
- package/ios/extensions/Notification.swift +17 -0
- package/ios/extensions/UIKeyboardAppearance.swift +21 -0
- package/ios/extensions/UIKeyboardType.swift +30 -0
- package/ios/observers/KeyboardMovementObserver.swift +8 -27
- package/ios/{traversal → protocols}/TextInput.swift +5 -1
- package/ios/traversal/ViewHierarchyNavigator.swift +4 -1
- package/jest/index.js +20 -15
- package/lib/commonjs/animated.js +6 -0
- package/lib/commonjs/animated.js.map +1 -1
- package/lib/commonjs/bindings.js +2 -2
- package/lib/commonjs/bindings.js.map +1 -1
- package/lib/commonjs/bindings.native.js +4 -3
- package/lib/commonjs/bindings.native.js.map +1 -1
- package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/commonjs/components/KeyboardStickyView/index.js +3 -2
- package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/commonjs/components/KeyboardToolbar/index.js +14 -9
- package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -1
- package/lib/commonjs/hooks/index.js +3 -3
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useWindowDimensions/index.android.js +5 -0
- package/lib/commonjs/hooks/useWindowDimensions/index.android.js.map +1 -1
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/internal.js +2 -2
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/module.js +45 -0
- package/lib/commonjs/module.js.map +1 -0
- package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/animated.js +6 -0
- package/lib/module/animated.js.map +1 -1
- package/lib/module/bindings.js +1 -1
- package/lib/module/bindings.js.map +1 -1
- package/lib/module/bindings.native.js +3 -2
- package/lib/module/bindings.native.js.map +1 -1
- package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/module/components/KeyboardStickyView/index.js +3 -2
- package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/module/components/KeyboardToolbar/index.js +15 -10
- package/lib/module/components/KeyboardToolbar/index.js.map +1 -1
- package/lib/module/hooks/index.js +1 -1
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useWindowDimensions/index.android.js +5 -0
- package/lib/module/hooks/useWindowDimensions/index.android.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal.js +2 -2
- package/lib/module/internal.js.map +1 -1
- package/lib/module/module.js +39 -0
- package/lib/module/module.js.map +1 -0
- package/lib/module/specs/NativeKeyboardController.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/bindings.d.ts +5 -6
- package/lib/typescript/bindings.native.d.ts +2 -3
- package/lib/typescript/components/KeyboardAvoidingView/index.d.ts +14 -24
- package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +6 -6
- package/lib/typescript/components/KeyboardAwareScrollView/utils.d.ts +1 -1
- package/lib/typescript/components/KeyboardStickyView/index.d.ts +8 -4
- package/lib/typescript/components/KeyboardToolbar/Button.d.ts +2 -1
- package/lib/typescript/components/KeyboardToolbar/index.d.ts +2 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/internal.d.ts +0 -1
- package/lib/typescript/module.d.ts +2 -0
- package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -1
- package/lib/typescript/types.d.ts +15 -2
- package/package.json +6 -4
- package/src/animated.tsx +6 -0
- package/src/bindings.native.ts +5 -4
- package/src/bindings.ts +2 -2
- package/src/components/KeyboardAvoidingView/index.tsx +27 -11
- package/src/components/KeyboardStickyView/index.tsx +12 -3
- package/src/components/KeyboardToolbar/index.tsx +14 -11
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useWindowDimensions/index.android.ts +5 -0
- package/src/index.ts +1 -0
- package/src/internal.ts +2 -2
- package/src/module.ts +50 -0
- package/src/specs/NativeKeyboardController.ts +1 -1
- package/src/types.ts +17 -1
package/android/build.gradle
CHANGED
|
@@ -158,14 +158,15 @@ repositories {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
def kotlin_version = getExtOrDefault('kotlinVersion')
|
|
161
|
+
def reactNativeVersion = getExtOrDefault('reactNativeVersion')
|
|
161
162
|
|
|
162
163
|
dependencies {
|
|
163
164
|
if (project.ext.shouldConsumeReactNativeFromMavenCentral()) {
|
|
164
165
|
// noinspection GradleDynamicVersion
|
|
165
|
-
implementation
|
|
166
|
+
implementation "com.facebook.react:react-android:$reactNativeVersion"
|
|
166
167
|
} else {
|
|
167
168
|
// noinspection GradleDynamicVersion
|
|
168
|
-
implementation
|
|
169
|
+
implementation "com.facebook.react:react-native:$reactNativeVersion"
|
|
169
170
|
}
|
|
170
171
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
171
172
|
implementation 'androidx.core:core-ktx:1.5.0-beta03'
|
|
@@ -4,23 +4,17 @@ def safeAppExtGet(prop, fallback) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// Let's detect react-native's directory, it will be used to determine RN's version
|
|
7
|
-
// https://github.com/software-mansion/react-native-reanimated/blob/
|
|
7
|
+
// https://github.com/software-mansion/react-native-reanimated/blob/36c291a15880c78a94dd125c51484630546ceb7c/packages/react-native-reanimated/android/build.gradle#L73
|
|
8
8
|
def resolveReactNativeDirectory() {
|
|
9
9
|
def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
|
|
10
10
|
if (reactNativeLocation != null) {
|
|
11
11
|
return file(reactNativeLocation)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return reactNativeFromProjectNodeModules
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
def reactNativeFromNodeModules = file("${projectDir}/../../react-native")
|
|
22
|
-
if (reactNativeFromNodeModules.exists()) {
|
|
23
|
-
return reactNativeFromNodeModules
|
|
14
|
+
// Fallback to node resolver for custom directory structures like monorepos.
|
|
15
|
+
def reactNativePackage = file(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim())
|
|
16
|
+
if (reactNativePackage.exists()) {
|
|
17
|
+
return reactNativePackage.parentFile
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
throw new GradleException(
|
|
@@ -2,6 +2,7 @@ package com.reactnativekeyboardcontroller.extensions
|
|
|
2
2
|
|
|
3
3
|
import android.os.Build
|
|
4
4
|
import android.text.Editable
|
|
5
|
+
import android.text.InputType
|
|
5
6
|
import android.text.TextWatcher
|
|
6
7
|
import android.view.View
|
|
7
8
|
import android.widget.EditText
|
|
@@ -110,6 +111,40 @@ fun EditText?.focus() {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
val EditText?.keyboardType: String
|
|
115
|
+
get() {
|
|
116
|
+
if (this == null) {
|
|
117
|
+
return "default"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Extract base input type class
|
|
121
|
+
val inputTypeClass = inputType and InputType.TYPE_MASK_CLASS
|
|
122
|
+
val inputTypeVariation = inputType and InputType.TYPE_MASK_VARIATION
|
|
123
|
+
|
|
124
|
+
// Check for special input types
|
|
125
|
+
return when {
|
|
126
|
+
inputTypeVariation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS -> "email-address"
|
|
127
|
+
inputTypeVariation == InputType.TYPE_TEXT_VARIATION_URI -> "url"
|
|
128
|
+
inputTypeVariation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD -> "visible-password"
|
|
129
|
+
|
|
130
|
+
// Check for specific input type classes
|
|
131
|
+
inputTypeClass == InputType.TYPE_CLASS_NUMBER ->
|
|
132
|
+
when {
|
|
133
|
+
(inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL) != 0 &&
|
|
134
|
+
(inputType and InputType.TYPE_NUMBER_FLAG_SIGNED) == 0 -> "decimal-pad"
|
|
135
|
+
|
|
136
|
+
(inputType and InputType.TYPE_NUMBER_FLAG_SIGNED) != 0 -> "numeric"
|
|
137
|
+
|
|
138
|
+
else -> "number-pad"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
inputTypeClass == InputType.TYPE_CLASS_PHONE -> "phone-pad"
|
|
142
|
+
inputTypeClass == InputType.TYPE_CLASS_TEXT -> "default"
|
|
143
|
+
|
|
144
|
+
else -> "default"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
113
148
|
class KeyboardControllerSelectionWatcher(
|
|
114
149
|
private val editText: ReactEditText,
|
|
115
150
|
private val action: (start: Int, end: Int, startX: Double, startY: Double, endX: Double, endY: Double) -> Unit,
|
package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.reactnativekeyboardcontroller.extensions
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Build
|
|
3
5
|
import com.facebook.react.bridge.ReactContext
|
|
4
6
|
import com.facebook.react.bridge.WritableMap
|
|
5
7
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
@@ -34,3 +36,19 @@ fun ThemedReactContext?.emitEvent(
|
|
|
34
36
|
|
|
35
37
|
Logger.i("ThemedReactContext", event)
|
|
36
38
|
}
|
|
39
|
+
|
|
40
|
+
val ThemedReactContext?.appearance: String
|
|
41
|
+
get() =
|
|
42
|
+
when {
|
|
43
|
+
this == null -> "default"
|
|
44
|
+
isSystemDarkMode(this) -> "dark"
|
|
45
|
+
else -> "light"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private fun isSystemDarkMode(context: Context): Boolean =
|
|
49
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
50
|
+
(context.getSystemService(Context.UI_MODE_SERVICE) as? android.app.UiModeManager)
|
|
51
|
+
?.nightMode == android.app.UiModeManager.MODE_NIGHT_YES
|
|
52
|
+
} else {
|
|
53
|
+
false
|
|
54
|
+
}
|
|
@@ -15,12 +15,15 @@ import com.facebook.react.views.textinput.ReactEditText
|
|
|
15
15
|
import com.facebook.react.views.view.ReactViewGroup
|
|
16
16
|
import com.reactnativekeyboardcontroller.constants.Keyboard
|
|
17
17
|
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
|
|
18
|
+
import com.reactnativekeyboardcontroller.extensions.appearance
|
|
18
19
|
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
|
|
19
20
|
import com.reactnativekeyboardcontroller.extensions.dp
|
|
20
21
|
import com.reactnativekeyboardcontroller.extensions.emitEvent
|
|
21
22
|
import com.reactnativekeyboardcontroller.extensions.isKeyboardAnimation
|
|
23
|
+
import com.reactnativekeyboardcontroller.extensions.keyboardType
|
|
22
24
|
import com.reactnativekeyboardcontroller.interactive.InteractiveKeyboardProvider
|
|
23
25
|
import com.reactnativekeyboardcontroller.log.Logger
|
|
26
|
+
import com.reactnativekeyboardcontroller.traversal.FocusedInputHolder
|
|
24
27
|
import kotlin.math.abs
|
|
25
28
|
|
|
26
29
|
private val TAG = KeyboardAnimationCallback::class.qualifiedName
|
|
@@ -425,6 +428,8 @@ class KeyboardAnimationCallback(
|
|
|
425
428
|
params.putInt("duration", duration)
|
|
426
429
|
params.putDouble("timestamp", System.currentTimeMillis().toDouble())
|
|
427
430
|
params.putInt("target", viewTagFocused)
|
|
431
|
+
params.putString("type", FocusedInputHolder.get()?.keyboardType)
|
|
432
|
+
params.putString("appearance", context.appearance)
|
|
428
433
|
|
|
429
434
|
return params
|
|
430
435
|
}
|
package/android/src/main/java/com/reactnativekeyboardcontroller/modal/ModalAttachedWatcher.kt
CHANGED
|
@@ -65,14 +65,17 @@ class ModalAttachedWatcher(
|
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
rootView.addView(eventView)
|
|
68
|
-
// on Android < 12 all events for `WindowInsetsAnimationCallback`
|
|
69
|
-
// go through main `rootView`, so we don't need to stop main
|
|
70
|
-
// callback - otherwise keyboard transitions will not be animated
|
|
71
|
-
this.callback()?.suspend(areEventsComingFromOwnWindow)
|
|
72
|
-
ViewCompat.setWindowInsetsAnimationCallback(rootView, callback)
|
|
73
|
-
ViewCompat.setOnApplyWindowInsetsListener(eventView, callback)
|
|
74
68
|
|
|
75
69
|
if (areEventsComingFromOwnWindow) {
|
|
70
|
+
// on Android < 12 all events for `WindowInsetsAnimationCallback`
|
|
71
|
+
// go through main `rootView`, so we don't need to stop main
|
|
72
|
+
// callback - otherwise keyboard transitions will not be animated
|
|
73
|
+
this.callback()?.suspend(true)
|
|
74
|
+
// attaching callback to Modal on Android < 12 can cause ghost animations, see: https://github.com/kirillzyusko/react-native-keyboard-controller/pull/718
|
|
75
|
+
// and overall attaching additional callbacks (if animation events go through the main window) is not necessary
|
|
76
|
+
ViewCompat.setWindowInsetsAnimationCallback(rootView, callback)
|
|
77
|
+
ViewCompat.setOnApplyWindowInsetsListener(eventView, callback)
|
|
78
|
+
|
|
76
79
|
// when modal is shown then keyboard will be hidden by default
|
|
77
80
|
//
|
|
78
81
|
// - if events are coming from main window - then keyboard position
|
|
@@ -22,19 +22,28 @@ class KeyboardControllerModuleImpl(
|
|
|
22
22
|
setSoftInputMode(mDefaultMode)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
fun dismiss() {
|
|
25
|
+
fun dismiss(keepFocus: Boolean) {
|
|
26
26
|
val activity = mReactContext.currentActivity
|
|
27
27
|
val view: View? = FocusedInputHolder.get()
|
|
28
28
|
|
|
29
29
|
if (view != null) {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
UiThreadUtil.runOnUiThread {
|
|
31
|
+
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
32
|
+
imm?.hideSoftInputFromWindow(view.windowToken, 0)
|
|
33
|
+
if (!keepFocus) {
|
|
34
|
+
view.clearFocus()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
fun setFocusTo(direction: String) {
|
|
36
41
|
if (direction == "current") {
|
|
37
|
-
|
|
42
|
+
UiThreadUtil.runOnUiThread {
|
|
43
|
+
FocusedInputHolder.focus()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
val view: View? = FocusedInputHolder.get()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller.views.overlay
|
|
2
|
+
|
|
3
|
+
import android.view.MotionEvent
|
|
4
|
+
import android.view.ViewGroup
|
|
5
|
+
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
6
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
7
|
+
import java.lang.reflect.Method
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Compat layer for `JSPointerDispatcher` interface for RN < 0.72
|
|
11
|
+
*/
|
|
12
|
+
class JSPointerDispatcherCompat(
|
|
13
|
+
viewGroup: ViewGroup,
|
|
14
|
+
) : JSPointerDispatcher(viewGroup) {
|
|
15
|
+
private val handleMotionEventMethod: Method? by lazy {
|
|
16
|
+
try {
|
|
17
|
+
// Try to get the 3-parameter method (for RN >= 0.72)
|
|
18
|
+
JSPointerDispatcher::class.java.getMethod(
|
|
19
|
+
HANDLE_MOTION_EVENT,
|
|
20
|
+
MotionEvent::class.java,
|
|
21
|
+
EventDispatcher::class.java,
|
|
22
|
+
Boolean::class.javaPrimitiveType,
|
|
23
|
+
)
|
|
24
|
+
} catch (_: NoSuchMethodException) {
|
|
25
|
+
try {
|
|
26
|
+
// Fallback to 2-parameter method (for RN < 0.72)
|
|
27
|
+
JSPointerDispatcher::class.java.getMethod(
|
|
28
|
+
HANDLE_MOTION_EVENT,
|
|
29
|
+
MotionEvent::class.java,
|
|
30
|
+
EventDispatcher::class.java,
|
|
31
|
+
)
|
|
32
|
+
} catch (_: NoSuchMethodException) {
|
|
33
|
+
null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fun handleMotionEventCompat(
|
|
39
|
+
event: MotionEvent?,
|
|
40
|
+
eventDispatcher: EventDispatcher?,
|
|
41
|
+
isCapture: Boolean,
|
|
42
|
+
) {
|
|
43
|
+
handleMotionEventMethod?.let { method ->
|
|
44
|
+
if (method.parameterCount == RN_72_PARAMS_COUNT) {
|
|
45
|
+
method.invoke(this, event, eventDispatcher, isCapture)
|
|
46
|
+
} else {
|
|
47
|
+
method.invoke(this, event, eventDispatcher)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
companion object {
|
|
53
|
+
private const val HANDLE_MOTION_EVENT = "handleMotionEvent"
|
|
54
|
+
private const val RN_72_PARAMS_COUNT = 3
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -8,7 +8,6 @@ import android.view.View
|
|
|
8
8
|
import android.view.WindowManager
|
|
9
9
|
import com.facebook.react.bridge.UiThreadUtil
|
|
10
10
|
import com.facebook.react.config.ReactFeatureFlags
|
|
11
|
-
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
12
11
|
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
13
12
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
14
13
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
@@ -99,13 +98,13 @@ class OverKeyboardRootViewGroup(
|
|
|
99
98
|
) : ReactViewGroup(reactContext),
|
|
100
99
|
RootViewCompat {
|
|
101
100
|
private val jsTouchDispatcher: JSTouchDispatcher = JSTouchDispatcher(this)
|
|
102
|
-
private var jsPointerDispatcher:
|
|
101
|
+
private var jsPointerDispatcher: JSPointerDispatcherCompat? = null
|
|
103
102
|
internal var eventDispatcher: EventDispatcher? = null
|
|
104
103
|
internal var isAttached = false
|
|
105
104
|
|
|
106
105
|
init {
|
|
107
106
|
if (ReactFeatureFlags.dispatchPointerEvents) {
|
|
108
|
-
jsPointerDispatcher =
|
|
107
|
+
jsPointerDispatcher = JSPointerDispatcherCompat(this)
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
|
|
@@ -125,7 +124,7 @@ class OverKeyboardRootViewGroup(
|
|
|
125
124
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
126
125
|
eventDispatcher?.let { eventDispatcher ->
|
|
127
126
|
jsTouchDispatcher.handleTouchEvent(event, eventDispatcher)
|
|
128
|
-
jsPointerDispatcher?.
|
|
127
|
+
jsPointerDispatcher?.handleMotionEventCompat(event, eventDispatcher, true)
|
|
129
128
|
}
|
|
130
129
|
return super.onInterceptTouchEvent(event)
|
|
131
130
|
}
|
|
@@ -134,7 +133,7 @@ class OverKeyboardRootViewGroup(
|
|
|
134
133
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
135
134
|
eventDispatcher?.let { eventDispatcher ->
|
|
136
135
|
jsTouchDispatcher.handleTouchEvent(event, eventDispatcher)
|
|
137
|
-
jsPointerDispatcher?.
|
|
136
|
+
jsPointerDispatcher?.handleMotionEventCompat(event, eventDispatcher, false)
|
|
138
137
|
}
|
|
139
138
|
super.onTouchEvent(event)
|
|
140
139
|
// In case when there is no children interested in handling touch event, we return true from
|
|
@@ -143,12 +142,16 @@ class OverKeyboardRootViewGroup(
|
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
|
|
146
|
-
eventDispatcher?.let {
|
|
145
|
+
eventDispatcher?.let {
|
|
146
|
+
jsPointerDispatcher?.handleMotionEventCompat(event, it, true)
|
|
147
|
+
}
|
|
147
148
|
return super.onHoverEvent(event)
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
override fun onHoverEvent(event: MotionEvent): Boolean {
|
|
151
|
-
eventDispatcher?.let {
|
|
152
|
+
eventDispatcher?.let {
|
|
153
|
+
jsPointerDispatcher?.handleMotionEventCompat(event, it, false)
|
|
154
|
+
}
|
|
152
155
|
return super.onHoverEvent(event)
|
|
153
156
|
}
|
|
154
157
|
|
|
@@ -68,13 +68,13 @@ RCT_EXPORT_METHOD(setInputMode : (nonnull NSNumber *)mode)
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
71
|
-
- (void)dismiss
|
|
71
|
+
- (void)dismiss:(BOOL)keepFocus
|
|
72
72
|
#else
|
|
73
|
-
RCT_EXPORT_METHOD(dismiss)
|
|
73
|
+
RCT_EXPORT_METHOD(dismiss : (BOOL)keepFocus)
|
|
74
74
|
#endif
|
|
75
75
|
{
|
|
76
76
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
77
|
-
[
|
|
77
|
+
[KeyboardControllerModuleImpl dismiss:keepFocus];
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// KeyboardControllerModuleImpl.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 19/11/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
@objc(KeyboardControllerModuleImpl)
|
|
12
|
+
public class KeyboardControllerModuleImpl: NSObject {
|
|
13
|
+
private static let keyboardRevealGestureName = "keyboardRevealGesture"
|
|
14
|
+
|
|
15
|
+
@objc
|
|
16
|
+
public static func dismiss(_ keepFocus: Bool) {
|
|
17
|
+
guard let input = UIResponder.current as? TextInput else { return }
|
|
18
|
+
|
|
19
|
+
if keepFocus {
|
|
20
|
+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTextInputTapped(_:)))
|
|
21
|
+
tapGesture.name = keyboardRevealGestureName
|
|
22
|
+
input.addGestureRecognizer(tapGesture)
|
|
23
|
+
|
|
24
|
+
input.inputView = UIView()
|
|
25
|
+
input.reloadInputViews()
|
|
26
|
+
|
|
27
|
+
NotificationCenter.default.addObserver(
|
|
28
|
+
self,
|
|
29
|
+
selector: #selector(onResponderResigned(_:)),
|
|
30
|
+
name: UITextField.textDidEndEditingNotification,
|
|
31
|
+
object: input
|
|
32
|
+
)
|
|
33
|
+
} else {
|
|
34
|
+
input.resignFirstResponder()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@objc static func onTextInputTapped(_ gesture: UITapGestureRecognizer) {
|
|
39
|
+
if gesture.state == .ended {
|
|
40
|
+
guard let input = UIResponder.current as? TextInput else { return }
|
|
41
|
+
|
|
42
|
+
cleanup(input)
|
|
43
|
+
|
|
44
|
+
input.becomeFirstResponder()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@objc static func onResponderResigned(_ notification: Notification) {
|
|
49
|
+
guard let input = notification.object as? TextInput else { return }
|
|
50
|
+
|
|
51
|
+
cleanup(input)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static func cleanup(_ input: TextInput) {
|
|
55
|
+
input.inputView = nil
|
|
56
|
+
input.reloadInputViews()
|
|
57
|
+
|
|
58
|
+
if let gestures = input.gestureRecognizers {
|
|
59
|
+
for gesture in gestures where gesture.name == keyboardRevealGestureName {
|
|
60
|
+
input.removeGestureRecognizer(gesture)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: input)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// KeyboardEventEmitterPayload.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 07/12/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
public func buildEventParams(_ height: Double, _ duration: Int, _ tag: NSNumber) -> [AnyHashable: Any] {
|
|
12
|
+
var data = [AnyHashable: Any]()
|
|
13
|
+
let input = FocusedInputHolder.shared.get()
|
|
14
|
+
|
|
15
|
+
data["height"] = height
|
|
16
|
+
data["duration"] = duration
|
|
17
|
+
data["timestamp"] = Date.currentTimeStamp
|
|
18
|
+
data["target"] = tag
|
|
19
|
+
data["type"] = input?.keyboardType.name ?? "default"
|
|
20
|
+
data["appearance"] = input?.keyboardAppearance.name ?? "default"
|
|
21
|
+
|
|
22
|
+
return data
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Notification.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 04/11/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
extension Notification {
|
|
9
|
+
func keyboardMetaData() -> (Int, NSValue?) {
|
|
10
|
+
let duration = Int(
|
|
11
|
+
(userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0) * 1000
|
|
12
|
+
)
|
|
13
|
+
let keyboardFrame = userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
|
|
14
|
+
|
|
15
|
+
return (duration, keyboardFrame)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//
|
|
2
|
+
// UIKeyboardAppearance.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 08/12/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
public extension UIKeyboardAppearance {
|
|
12
|
+
var name: String {
|
|
13
|
+
switch self {
|
|
14
|
+
case .default: return "default"
|
|
15
|
+
case .dark: return "dark"
|
|
16
|
+
case .light: return "light"
|
|
17
|
+
@unknown default:
|
|
18
|
+
return "default"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// UIKeyboardType.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 08/12/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
extension UIKeyboardType {
|
|
12
|
+
private static let keyboardTypeToStringMapping: [UIKeyboardType: String] = [
|
|
13
|
+
.default: "default",
|
|
14
|
+
.asciiCapable: "ascii-capable",
|
|
15
|
+
.numbersAndPunctuation: "numbers-and-punctuation",
|
|
16
|
+
.URL: "url",
|
|
17
|
+
.numberPad: "number-pad",
|
|
18
|
+
.phonePad: "phone-pad",
|
|
19
|
+
.namePhonePad: "name-phone-pad",
|
|
20
|
+
.emailAddress: "email-address",
|
|
21
|
+
.decimalPad: "decimal-pad",
|
|
22
|
+
.twitter: "twitter",
|
|
23
|
+
.webSearch: "web-search",
|
|
24
|
+
.asciiCapableNumberPad: "ascii-capable-number-pad",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
var name: String {
|
|
28
|
+
return UIKeyboardType.keyboardTypeToStringMapping[self] ?? "default"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -163,7 +163,7 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
@objc func keyboardWillAppear(_ notification: Notification) {
|
|
166
|
-
let (duration, frame) =
|
|
166
|
+
let (duration, frame) = notification.keyboardMetaData()
|
|
167
167
|
if let keyboardFrame = frame {
|
|
168
168
|
tag = UIResponder.current.reactViewTag
|
|
169
169
|
let keyboardHeight = keyboardFrame.cgRectValue.size.height
|
|
@@ -173,7 +173,7 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
173
173
|
|
|
174
174
|
onRequestAnimation()
|
|
175
175
|
onEvent("onKeyboardMoveStart", Float(keyboardHeight) as NSNumber, 1, duration as NSNumber, tag)
|
|
176
|
-
onNotify("KeyboardController::keyboardWillShow",
|
|
176
|
+
onNotify("KeyboardController::keyboardWillShow", buildEventParams(keyboardHeight, duration, tag))
|
|
177
177
|
|
|
178
178
|
setupKeyboardWatcher()
|
|
179
179
|
initializeAnimation(fromValue: prevKeyboardPosition, toValue: keyboardHeight)
|
|
@@ -181,13 +181,13 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
@objc func keyboardWillDisappear(_ notification: Notification) {
|
|
184
|
-
let (duration, _) =
|
|
184
|
+
let (duration, _) = notification.keyboardMetaData()
|
|
185
185
|
tag = UIResponder.current.reactViewTag
|
|
186
186
|
self.duration = duration
|
|
187
187
|
|
|
188
188
|
onRequestAnimation()
|
|
189
189
|
onEvent("onKeyboardMoveStart", 0, 0, duration as NSNumber, tag)
|
|
190
|
-
onNotify("KeyboardController::keyboardWillHide",
|
|
190
|
+
onNotify("KeyboardController::keyboardWillHide", buildEventParams(0, duration, tag))
|
|
191
191
|
|
|
192
192
|
setupKeyboardWatcher()
|
|
193
193
|
removeKVObserver()
|
|
@@ -196,7 +196,7 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
196
196
|
|
|
197
197
|
@objc func keyboardDidAppear(_ notification: Notification) {
|
|
198
198
|
let timestamp = Date.currentTimeStamp
|
|
199
|
-
let (duration, frame) =
|
|
199
|
+
let (duration, frame) = notification.keyboardMetaData()
|
|
200
200
|
if let keyboardFrame = frame {
|
|
201
201
|
let (position, _) = keyboardView.frameTransitionInWindow
|
|
202
202
|
let keyboardHeight = keyboardFrame.cgRectValue.size.height
|
|
@@ -210,7 +210,7 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
210
210
|
|
|
211
211
|
onCancelAnimation()
|
|
212
212
|
onEvent("onKeyboardMoveEnd", height as NSNumber, progress as NSNumber, duration as NSNumber, tag)
|
|
213
|
-
onNotify("KeyboardController::keyboardDidShow",
|
|
213
|
+
onNotify("KeyboardController::keyboardDidShow", buildEventParams(height, duration, tag))
|
|
214
214
|
|
|
215
215
|
removeKeyboardWatcher()
|
|
216
216
|
setupKVObserver()
|
|
@@ -219,12 +219,12 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
@objc func keyboardDidDisappear(_ notification: Notification) {
|
|
222
|
-
let (duration, _) =
|
|
222
|
+
let (duration, _) = notification.keyboardMetaData()
|
|
223
223
|
tag = UIResponder.current.reactViewTag
|
|
224
224
|
|
|
225
225
|
onCancelAnimation()
|
|
226
226
|
onEvent("onKeyboardMoveEnd", 0 as NSNumber, 0, duration as NSNumber, tag)
|
|
227
|
-
onNotify("KeyboardController::keyboardDidHide",
|
|
227
|
+
onNotify("KeyboardController::keyboardDidHide", buildEventParams(0, duration, tag))
|
|
228
228
|
|
|
229
229
|
removeKeyboardWatcher()
|
|
230
230
|
animation = nil
|
|
@@ -309,23 +309,4 @@ public class KeyboardMovementObserver: NSObject {
|
|
|
309
309
|
tag
|
|
310
310
|
)
|
|
311
311
|
}
|
|
312
|
-
|
|
313
|
-
private func metaDataFromNotification(_ notification: Notification) -> (Int, NSValue?) {
|
|
314
|
-
let duration = Int(
|
|
315
|
-
(notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0) * 1000
|
|
316
|
-
)
|
|
317
|
-
let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
|
|
318
|
-
|
|
319
|
-
return (duration, keyboardFrame)
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
private func getEventParams(_ height: Double, _ duration: Int) -> [AnyHashable: Any] {
|
|
323
|
-
var data = [AnyHashable: Any]()
|
|
324
|
-
data["height"] = height
|
|
325
|
-
data["duration"] = duration
|
|
326
|
-
data["timestamp"] = Date.currentTimeStamp
|
|
327
|
-
data["target"] = tag
|
|
328
|
-
|
|
329
|
-
return data
|
|
330
|
-
}
|
|
331
312
|
}
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
import Foundation
|
|
10
10
|
import UIKit
|
|
11
11
|
|
|
12
|
-
public protocol TextInput:
|
|
12
|
+
public protocol TextInput: UIView {
|
|
13
|
+
// default common methods/properties
|
|
14
|
+
var inputView: UIView? { get set }
|
|
15
|
+
var keyboardType: UIKeyboardType { get }
|
|
16
|
+
var keyboardAppearance: UIKeyboardAppearance { get }
|
|
13
17
|
func focus()
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -14,7 +14,10 @@ public class ViewHierarchyNavigator: NSObject {
|
|
|
14
14
|
@objc public static func setFocusTo(direction: String) {
|
|
15
15
|
DispatchQueue.main.async {
|
|
16
16
|
if direction == "current" {
|
|
17
|
-
FocusedInputHolder.shared.
|
|
17
|
+
let input = FocusedInputHolder.shared.get()
|
|
18
|
+
input?.inputView = nil
|
|
19
|
+
input?.reloadInputViews()
|
|
20
|
+
input?.focus()
|
|
18
21
|
return
|
|
19
22
|
}
|
|
20
23
|
|