From 8c6a98400ee861fec757dd7135b217783852be70 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 30 Dec 2021 06:37:32 -0800 Subject: [PATCH] Introduce TextInput.onChangeSync Summary: changelog: [internal] Add experimental `TextInput.onChangeSync` which delivers onChange event synchronously. Reviewed By: ShikaSD Differential Revision: D33188083 fbshipit-source-id: 1e1dcd0d71c7eec98d3d5f69967659e07ac4e6a6 --- Libraries/Components/TextInput/TextInput.js | 48 ++++++++++++++++++- .../__snapshots__/TextInput-test.js.snap | 2 + .../Text/TextInput/RCTBaseTextInputView.h | 1 + .../TextInput/RCTBaseTextInputViewManager.m | 1 + .../TextInput/RCTTextInputComponentView.mm | 8 +++- .../iostextinput/TextInputEventEmitter.cpp | 6 +++ .../iostextinput/TextInputEventEmitter.h | 1 + .../textinput/iostextinput/TextInputProps.cpp | 6 +++ .../textinput/iostextinput/TextInputProps.h | 1 + 9 files changed, 72 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index cb0d18d54ff15c..c92dc3a173411d 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -598,12 +598,33 @@ export type Props = $ReadOnly<{| */ onChange?: ?(e: ChangeEvent) => 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 the text input's text changes. + * + * @platform ios + */ + unstable_onChangeSync?: ?(e: ChangeEvent) => mixed, + /** * Callback that is called when the text input's text changes. * Changed text is passed as an argument to the callback handler. */ onChangeText?: ?(text: string) => 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 the text input's text changes. + * Changed text is passed as an argument to the callback handler. + * + * @platform ios + */ + unstable_onChangeTextSync?: ?(text: string) => mixed, + /** * Callback that is called when the text input's content size changes. * This will be called with @@ -643,7 +664,7 @@ export type Props = $ReadOnly<{| * the typed-in character otherwise including `' '` for space. * Fires before `onChange` callbacks. * - * Only available in Fabric on iOS. + * @platform ios */ unstable_onKeyPressSync?: ?(e: KeyPressEvent) => mixed, @@ -1101,6 +1122,26 @@ function InternalTextInput(props: Props): React.Node { setMostRecentEventCount(event.nativeEvent.eventCount); }; + const _onChangeSync = (event: ChangeEvent) => { + const currentText = event.nativeEvent.text; + props.unstable_onChangeSync && props.unstable_onChangeSync(event); + props.unstable_onChangeTextSync && + props.unstable_onChangeTextSync(currentText); + + if (inputRef.current == null) { + // calling `props.onChange` or `props.onChangeText` + // may clean up the input itself. Exits here. + return; + } + + setLastNativeText(currentText); + // This must happen last, after we call setLastNativeText. + // Different ordering can cause bugs when editing AndroidTextInputs + // with multiple Fragments. + // We must update this so that controlled input updates work. + setMostRecentEventCount(event.nativeEvent.eventCount); + }; + const _onSelectionChange = (event: SelectionChangeEvent) => { props.onSelectionChange && props.onSelectionChange(event); @@ -1187,6 +1228,10 @@ function InternalTextInput(props: Props): React.Node { ? [styles.multilineInput, props.style] : props.style; + const useOnChangeSync = + (props.unstable_onChangeSync || props.unstable_onChangeTextSync) && + !(props.onChange || props.onChangeText); + textInput = ( (_eventEmitter)->onChange([self _textInputMetrics]); + auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); + auto const &props = *std::static_pointer_cast(_props); + if (props.onChangeSync) { + textInputEventEmitter.onChangeSync([self _textInputMetrics]); + } else { + textInputEventEmitter.onChange([self _textInputMetrics]); + } } } diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp index 92172e2db1a9bd..b6dd2e971d1e92 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.cpp @@ -75,6 +75,12 @@ void TextInputEventEmitter::onChange( dispatchTextInputEvent("change", textInputMetrics); } +void TextInputEventEmitter::onChangeSync( + TextInputMetrics const &textInputMetrics) const { + dispatchTextInputEvent( + "changeSync", textInputMetrics, EventPriority::SynchronousBatched); +} + void TextInputEventEmitter::onContentSizeChange( TextInputMetrics const &textInputMetrics) const { dispatchTextInputEvent("contentSizeChange", textInputMetrics); diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h index e241a2d0d1cb73..93fff922943b31 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputEventEmitter.h @@ -40,6 +40,7 @@ class TextInputEventEmitter : public ViewEventEmitter { void onFocus(TextInputMetrics const &textInputMetrics) const; void onBlur(TextInputMetrics const &textInputMetrics) const; void onChange(TextInputMetrics const &textInputMetrics) const; + void onChangeSync(TextInputMetrics const &textInputMetrics) const; void onContentSizeChange(TextInputMetrics const &textInputMetrics) const; void onSelectionChange(TextInputMetrics const &textInputMetrics) const; void onEndEditing(TextInputMetrics const &textInputMetrics) const; diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp index 879278e7033c27..c0a2f0887e1da8 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.cpp @@ -99,6 +99,12 @@ TextInputProps::TextInputProps( rawProps, "onKeyPressSync", sourceProps.onKeyPressSync, + {})), + onChangeSync(convertRawProp( + context, + rawProps, + "onChangeSync", + sourceProps.onChangeSync, {})){}; TextAttributes TextInputProps::getEffectiveTextAttributes( diff --git a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h index 59355f01c11fc7..8b0a748df8c7c6 100644 --- a/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h +++ b/ReactCommon/react/renderer/components/textinput/iostextinput/TextInputProps.h @@ -63,6 +63,7 @@ class TextInputProps final : public ViewProps, public BaseTextProps { std::string const inputAccessoryViewID{}; bool onKeyPressSync{false}; + bool onChangeSync{false}; /* * Accessors