From f5f6fd70f2eb728759da7a4a9c83557a649bb4d0 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 7 Dec 2021 13:40:34 -0800 Subject: [PATCH] Introduce TextInput.onKeyPressSync Summary: changelog: [internal] Introduce a way to execute `onKeyPress` synchronously. This feature is experimental and will be changed in the future. It is not decided if marking native events as "sync" is going to be path forward with synchronous access. NOTE: This is experimental API. Reviewed By: ShikaSD Differential Revision: D32882092 fbshipit-source-id: 68c66a9bb7c97758219e085c88a77f3c475c1eb3 --- Libraries/Components/TextInput/TextInput.js | 16 ++++++++++++++++ .../Text/TextInput/RCTBaseTextInputViewManager.m | 1 + .../TextInput/RCTTextInputComponentView.mm | 11 +++++++++-- .../iostextinput/TextInputEventEmitter.cpp | 10 ++++++++++ .../iostextinput/TextInputEventEmitter.h | 1 + .../textinput/iostextinput/TextInputProps.cpp | 6 ++++++ .../textinput/iostextinput/TextInputProps.h | 2 ++ 7 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 96f64d82e9f..cb0d18d54ff 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -632,6 +632,21 @@ export type Props = $ReadOnly<{| */ onKeyPress?: ?(e: KeyPressEvent) => mixed, + /** + * DANGER: this API is not stable and will change in the future. + * + * Callback will be called on the main thread and may result in dropped frames. + * + * Callback that is called when a key is pressed. + * This will be called with `{ nativeEvent: { key: keyValue } }` + * where `keyValue` is `'Enter'` or `'Backspace'` for respective keys and + * the typed-in character otherwise including `' '` for space. + * Fires before `onChange` callbacks. + * + * Only available in Fabric on iOS. + */ + unstable_onKeyPressSync?: ?(e: KeyPressEvent) => mixed, + /** * Called when a touch is engaged. */ @@ -1184,6 +1199,7 @@ function InternalTextInput(props: Props): React.Node { focusable={focusable} mostRecentEventCount={mostRecentEventCount} onBlur={_onBlur} + onKeyPressSync={props.unstable_onKeyPressSync} onChange={_onChange} onContentSizeChange={props.onContentSizeChange} onFocus={_onFocus} diff --git a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m index 77d70d14c76..1deb6f6605e 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputViewManager.m @@ -61,6 +61,7 @@ RCT_EXPORT_VIEW_PROPERTY(textContentType, NSString) RCT_EXPORT_VIEW_PROPERTY(passwordRules, NSString) RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onKeyPressSync, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 7123913d9ed..95b0ccae315 100644 --- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -323,16 +323,23 @@ using namespace facebook::react; - (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range { + auto const &props = *std::static_pointer_cast(_props); + if (!_backedTextInputView.textWasPasted) { if (_eventEmitter) { KeyPressMetrics keyPressMetrics; keyPressMetrics.text = RCTStringFromNSString(text); keyPressMetrics.eventCount = _mostRecentEventCount; - std::static_pointer_cast(_eventEmitter)->onKeyPress(keyPressMetrics); + + auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); + if (props.onKeyPressSync) { + textInputEventEmitter.onKeyPressSync(keyPressMetrics); + } else { + textInputEventEmitter.onKeyPress(keyPressMetrics); + } } } - auto const &props = *std::static_pointer_cast(_props); if (props.maxLength) { NSInteger allowedLength = props.maxLength - _backedTextInputView.attributedText.string.length + range.length; diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp index 13efb53cb6c..92172e2db1a 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp @@ -105,6 +105,16 @@ void TextInputEventEmitter::onKeyPress( EventPriority::AsynchronousBatched); } +void TextInputEventEmitter::onKeyPressSync( + KeyPressMetrics const &keyPressMetrics) const { + dispatchEvent( + "keyPressSync", + [keyPressMetrics](jsi::Runtime &runtime) { + return keyPressMetricsPayload(runtime, keyPressMetrics); + }, + EventPriority::SynchronousBatched); +} + void TextInputEventEmitter::onScroll( TextInputMetrics const &textInputMetrics) const { dispatchTextInputEvent("scroll", textInputMetrics); diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h index 10510ae5c57..e241a2d0d1c 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h @@ -45,6 +45,7 @@ class TextInputEventEmitter : public ViewEventEmitter { void onEndEditing(TextInputMetrics const &textInputMetrics) const; void onSubmitEditing(TextInputMetrics const &textInputMetrics) const; void onKeyPress(KeyPressMetrics const &textInputMetrics) const; + void onKeyPressSync(KeyPressMetrics const &textInputMetrics) const; void onScroll(TextInputMetrics const &textInputMetrics) const; private: diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp index 9e13cb97bb0..c51fde989ae 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp @@ -93,6 +93,12 @@ TextInputProps::TextInputProps( rawProps, "inputAccessoryViewID", sourceProps.inputAccessoryViewID, + {})), + onKeyPressSync(convertRawProp( + context, + rawProps, + "onKeyPressSync", + sourceProps.onKeyPressSync, {})){}; TextAttributes TextInputProps::getEffectiveTextAttributes( diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h index 9fb44a4b2c1..c78f8b55c02 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h @@ -62,6 +62,8 @@ class TextInputProps final : public ViewProps, public BaseTextProps { std::string const inputAccessoryViewID{}; + bool onKeyPressSync{false}; + /* * Accessors */