diff --git a/FabricExample/src/screens/Examples/Events/index.tsx b/FabricExample/src/screens/Examples/Events/index.tsx index 00c679fb66..a7d4bbc283 100644 --- a/FabricExample/src/screens/Examples/Events/index.tsx +++ b/FabricExample/src/screens/Examples/Events/index.tsx @@ -24,7 +24,7 @@ function EventsListener() { Toast.show({ type: "info", text1: "⬆️ ⌨️ Keyboard will show", - text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); const shown = KeyboardEvents.addListener("keyboardDidShow", (e) => { @@ -33,7 +33,7 @@ function EventsListener() { Toast.show({ type: "success", text1: "⌨️ Keyboard is shown", - text2: `👋 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `👋 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); const hide = KeyboardEvents.addListener("keyboardWillHide", (e) => { @@ -42,7 +42,7 @@ function EventsListener() { Toast.show({ type: "info", text1: "⬇️ ⌨️ Keyboard will hide", - text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); const hid = KeyboardEvents.addListener("keyboardDidHide", (e) => { @@ -51,7 +51,7 @@ function EventsListener() { Toast.show({ type: "error", text1: "⌨️ Keyboard is hidden", - text2: `🤐 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `🤐 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); @@ -63,7 +63,7 @@ function EventsListener() { }; }, []); - return ; + return ; } export default function Events() { diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt b/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt index f2dd54bd7e..5045eaec76 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt @@ -2,6 +2,7 @@ package com.reactnativekeyboardcontroller.extensions import android.os.Build import android.text.Editable +import android.text.InputType import android.text.TextWatcher import android.view.View import android.widget.EditText @@ -110,6 +111,40 @@ fun EditText?.focus() { } } +val EditText?.keyboardType: String + get() { + if (this == null) { + return "default" + } + + // Extract base input type class + val inputTypeClass = inputType and InputType.TYPE_MASK_CLASS + val inputTypeVariation = inputType and InputType.TYPE_MASK_VARIATION + + // Check for special input types + return when { + inputTypeVariation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS -> "email-address" + inputTypeVariation == InputType.TYPE_TEXT_VARIATION_URI -> "url" + inputTypeVariation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD -> "visible-password" + + // Check for specific input type classes + inputTypeClass == InputType.TYPE_CLASS_NUMBER -> + when { + (inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL) != 0 && + (inputType and InputType.TYPE_NUMBER_FLAG_SIGNED) == 0 -> "decimal-pad" + + (inputType and InputType.TYPE_NUMBER_FLAG_SIGNED) != 0 -> "numeric" + + else -> "number-pad" + } + + inputTypeClass == InputType.TYPE_CLASS_PHONE -> "phone-pad" + inputTypeClass == InputType.TYPE_CLASS_TEXT -> "default" + + else -> "default" + } + } + class KeyboardControllerSelectionWatcher( private val editText: ReactEditText, private val action: (start: Int, end: Int, startX: Double, startY: Double, endX: Double, endY: Double) -> Unit, diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt b/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt index 191591025d..2a06f8d6c0 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt @@ -19,8 +19,10 @@ import com.reactnativekeyboardcontroller.extensions.dispatchEvent import com.reactnativekeyboardcontroller.extensions.dp import com.reactnativekeyboardcontroller.extensions.emitEvent import com.reactnativekeyboardcontroller.extensions.isKeyboardAnimation +import com.reactnativekeyboardcontroller.extensions.keyboardType import com.reactnativekeyboardcontroller.interactive.InteractiveKeyboardProvider import com.reactnativekeyboardcontroller.log.Logger +import com.reactnativekeyboardcontroller.traversal.FocusedInputHolder import kotlin.math.abs private val TAG = KeyboardAnimationCallback::class.qualifiedName @@ -425,6 +427,7 @@ class KeyboardAnimationCallback( params.putInt("duration", duration) params.putDouble("timestamp", System.currentTimeMillis().toDouble()) params.putInt("target", viewTagFocused) + params.putString("type", FocusedInputHolder.get()?.keyboardType) return params } diff --git a/docs/docs/api/keyboard-events.md b/docs/docs/api/keyboard-events.md index 9920b5f0dd..c6e550c17c 100644 --- a/docs/docs/api/keyboard-events.md +++ b/docs/docs/api/keyboard-events.md @@ -16,10 +16,10 @@ keywords: This library exposes 4 events which are available on all platforms: -- keyboardWillShow -- keyboardWillHide -- keyboardDidShow -- keyboardDidHide +- `keyboardWillShow` - emitted when the keyboard is about to appear. +- `keyboardWillHide` - emitted when the keyboard is about to disappear. +- `keyboardDidShow` - emitted when the keyboard has completed its animation and is fully visible on the screen. +- `keyboardDidHide` - emitted when the keyboard has completed its animation and is fully hidden. ## Event structure @@ -31,6 +31,7 @@ type KeyboardEventData = { duration: number; // duration of the animation timestamp: number; // timestamp of the event from native thread target: number; // tag of the focused TextInput + type: string; // `keyboardType` property from focused `TextInput` }; ``` diff --git a/example/src/screens/Examples/Events/index.tsx b/example/src/screens/Examples/Events/index.tsx index 00c679fb66..a7d4bbc283 100644 --- a/example/src/screens/Examples/Events/index.tsx +++ b/example/src/screens/Examples/Events/index.tsx @@ -24,7 +24,7 @@ function EventsListener() { Toast.show({ type: "info", text1: "⬆️ ⌨️ Keyboard will show", - text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); const shown = KeyboardEvents.addListener("keyboardDidShow", (e) => { @@ -33,7 +33,7 @@ function EventsListener() { Toast.show({ type: "success", text1: "⌨️ Keyboard is shown", - text2: `👋 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `👋 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); const hide = KeyboardEvents.addListener("keyboardWillHide", (e) => { @@ -42,7 +42,7 @@ function EventsListener() { Toast.show({ type: "info", text1: "⬇️ ⌨️ Keyboard will hide", - text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `📲 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); const hid = KeyboardEvents.addListener("keyboardDidHide", (e) => { @@ -51,7 +51,7 @@ function EventsListener() { Toast.show({ type: "error", text1: "⌨️ Keyboard is hidden", - text2: `🤐 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms`, + text2: `🤐 Height: ${e.height}, duration: ${e.duration}ms, delay: ${delay}ms, type: ${e.type}`, }); }); @@ -63,7 +63,7 @@ function EventsListener() { }; }, []); - return ; + return ; } export default function Events() { diff --git a/ios/events/KeyboardEventEmitterPayload.swift b/ios/events/KeyboardEventEmitterPayload.swift new file mode 100644 index 0000000000..f4777d6bea --- /dev/null +++ b/ios/events/KeyboardEventEmitterPayload.swift @@ -0,0 +1,22 @@ +// +// KeyboardEventEmitterPayload.swift +// Pods +// +// Created by Kiryl Ziusko on 07/12/2024. +// + +import Foundation +import UIKit + +public func buildEventParams(_ height: Double, _ duration: Int, _ tag: NSNumber) -> [AnyHashable: Any] { + var data = [AnyHashable: Any]() + let input = FocusedInputHolder.shared.get() + + data["height"] = height + data["duration"] = duration + data["timestamp"] = Date.currentTimeStamp + data["target"] = tag + data["type"] = input?.keyboardType.name ?? "default" + + return data +} diff --git a/ios/extensions/UIKeyboardType.swift b/ios/extensions/UIKeyboardType.swift new file mode 100644 index 0000000000..65715f6147 --- /dev/null +++ b/ios/extensions/UIKeyboardType.swift @@ -0,0 +1,30 @@ +// +// UIKeyboardType.swift +// Pods +// +// Created by Kiryl Ziusko on 08/12/2024. +// + +import Foundation +import UIKit + +extension UIKeyboardType { + private static let keyboardTypeToStringMapping: [UIKeyboardType: String] = [ + .default: "default", + .asciiCapable: "ascii-capable", + .numbersAndPunctuation: "numbers-and-punctuation", + .URL: "url", + .numberPad: "number-pad", + .phonePad: "phone-pad", + .namePhonePad: "name-phone-pad", + .emailAddress: "email-address", + .decimalPad: "decimal-pad", + .twitter: "twitter", + .webSearch: "web-search", + .asciiCapableNumberPad: "ascii-capable-number-pad", + ] + + var name: String { + return UIKeyboardType.keyboardTypeToStringMapping[self] ?? "default" + } +} diff --git a/ios/observers/KeyboardMovementObserver.swift b/ios/observers/KeyboardMovementObserver.swift index 68871b15a5..d281499d09 100644 --- a/ios/observers/KeyboardMovementObserver.swift +++ b/ios/observers/KeyboardMovementObserver.swift @@ -173,7 +173,7 @@ public class KeyboardMovementObserver: NSObject { onRequestAnimation() onEvent("onKeyboardMoveStart", Float(keyboardHeight) as NSNumber, 1, duration as NSNumber, tag) - onNotify("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight, duration)) + onNotify("KeyboardController::keyboardWillShow", buildEventParams(keyboardHeight, duration, tag)) setupKeyboardWatcher() initializeAnimation(fromValue: prevKeyboardPosition, toValue: keyboardHeight) @@ -187,7 +187,7 @@ public class KeyboardMovementObserver: NSObject { onRequestAnimation() onEvent("onKeyboardMoveStart", 0, 0, duration as NSNumber, tag) - onNotify("KeyboardController::keyboardWillHide", getEventParams(0, duration)) + onNotify("KeyboardController::keyboardWillHide", buildEventParams(0, duration, tag)) setupKeyboardWatcher() removeKVObserver() @@ -210,7 +210,7 @@ public class KeyboardMovementObserver: NSObject { onCancelAnimation() onEvent("onKeyboardMoveEnd", height as NSNumber, progress as NSNumber, duration as NSNumber, tag) - onNotify("KeyboardController::keyboardDidShow", getEventParams(height, duration)) + onNotify("KeyboardController::keyboardDidShow", buildEventParams(height, duration, tag)) removeKeyboardWatcher() setupKVObserver() @@ -224,7 +224,7 @@ public class KeyboardMovementObserver: NSObject { onCancelAnimation() onEvent("onKeyboardMoveEnd", 0 as NSNumber, 0, duration as NSNumber, tag) - onNotify("KeyboardController::keyboardDidHide", getEventParams(0, duration)) + onNotify("KeyboardController::keyboardDidHide", buildEventParams(0, duration, tag)) removeKeyboardWatcher() animation = nil @@ -309,14 +309,4 @@ public class KeyboardMovementObserver: NSObject { tag ) } - - private func getEventParams(_ height: Double, _ duration: Int) -> [AnyHashable: Any] { - var data = [AnyHashable: Any]() - data["height"] = height - data["duration"] = duration - data["timestamp"] = Date.currentTimeStamp - data["target"] = tag - - return data - } } diff --git a/ios/protocols/TextInput.swift b/ios/protocols/TextInput.swift index deb1458453..f24c9ca6ae 100644 --- a/ios/protocols/TextInput.swift +++ b/ios/protocols/TextInput.swift @@ -10,6 +10,7 @@ import Foundation import UIKit public protocol TextInput: AnyObject { + var keyboardType: UIKeyboardType { get } func focus() } diff --git a/src/types.ts b/src/types.ts index b2801519cf..5dca359ad1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,7 @@ import type { PropsWithChildren } from "react"; import type { EmitterSubscription, NativeSyntheticEvent, + TextInputProps, ViewProps, } from "react-native"; @@ -146,6 +147,7 @@ export type KeyboardEventData = { duration: number; timestamp: number; target: number; + type: TextInputProps["keyboardType"]; }; export type KeyboardEventsModule = { addListener: (