Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composer: add clear command that bypasses the event count #46091

Merged
merged 19 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 0 additions & 66 deletions patches/react-native+0.73.4+022+textInputClear.patch

This file was deleted.

269 changes: 269 additions & 0 deletions patches/react-native+0.73.4+023+textinput-clear-command.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
index 88d3cc8..8e60c9e 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
@@ -97,6 +97,9 @@ const RCTTextInputViewConfig = {
topChangeSync: {
registrationName: 'onChangeSync',
},
+ topClear: {
+ registrationName: 'onClear',
+ },
},
validAttributes: {
fontSize: true,
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
index 2c0c099..26a477f 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
@@ -707,6 +707,13 @@ export interface TextInputProps
| ((e: NativeSyntheticEvent<TextInputFocusEventData>) => void)
| undefined;

+ /**
+ * Callback that is called when the text input was cleared using the native clear command.
+ */
+ onClear?:
+ | ((e: NativeSyntheticEvent<TextInputChangeEventData>) => void)
+ | undefined;
+
/**
* Callback that is called when the text input's text changes.
*/
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
index 481938f..346acaa 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
@@ -1329,6 +1329,11 @@ function InternalTextInput(props: Props): React.Node {
});
};

+ const _onClear = (event: ChangeEvent) => {
+ setMostRecentEventCount(event.nativeEvent.eventCount);
+ props.onClear && props.onClear(event);
+ };
+
const _onFocus = (event: FocusEvent) => {
TextInputState.focusInput(inputRef.current);
if (props.onFocus) {
@@ -1462,6 +1467,7 @@ function InternalTextInput(props: Props): React.Node {
nativeID={id ?? props.nativeID}
onBlur={_onBlur}
onKeyPressSync={props.unstable_onKeyPressSync}
+ onClear={_onClear}
onChange={_onChange}
onChangeSync={useOnChangeSync === true ? _onChangeSync : null}
onContentSizeChange={props.onContentSizeChange}
@@ -1516,6 +1522,7 @@ function InternalTextInput(props: Props): React.Node {
nativeID={id ?? props.nativeID}
numberOfLines={props.rows ?? props.numberOfLines}
onBlur={_onBlur}
+ onClear={_onClear}
onChange={_onChange}
onFocus={_onFocus}
/* $FlowFixMe[prop-missing] the types for AndroidTextInput don't match
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
index a19b555..4785987 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
@@ -62,6 +62,7 @@ @implementation RCTBaseTextInputViewManager {

RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onKeyPressSync, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onClear, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock)
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
index 7ce04da..70754bf 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
@@ -452,6 +452,19 @@ - (void)blur
[_backedTextInputView resignFirstResponder];
}

+- (void)clear
+{
+ auto metrics = [self _textInputMetrics];
+ [self setTextAndSelection:_mostRecentEventCount value:@"" start:0 end:0];
+
+ _mostRecentEventCount++;
+ metrics.eventCount = _mostRecentEventCount;
+
+ // Notify JS that the event counter has changed
+ const auto &textInputEventEmitter = static_cast<const TextInputEventEmitter &>(*_eventEmitter);
+ textInputEventEmitter.onClear(metrics);
+}
+
- (void)setTextAndSelection:(NSInteger)eventCount
value:(NSString *__nullable)value
start:(NSInteger)start
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h
index fe3376a..6889eed 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h
@@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol RCTTextInputViewProtocol <NSObject>
- (void)focus;
- (void)blur;
+- (void)clear;
- (void)setTextAndSelection:(NSInteger)eventCount
value:(NSString *__nullable)value
start:(NSInteger)start
@@ -49,6 +50,19 @@ RCTTextInputHandleCommand(id<RCTTextInputViewProtocol> componentView, const NSSt
return;
}

+ if ([commandName isEqualToString:@"clear"]) {
+#if RCT_DEBUG
+ if ([args count] != 0) {
+ RCTLogError(
+ @"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0);
+ return;
+ }
+#endif
+
+ [componentView clear];
+ return;
+ }
+
if ([commandName isEqualToString:@"setTextAndSelection"]) {
#if RCT_DEBUG
if ([args count] != 4) {
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextClearEvent.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextClearEvent.java
new file mode 100644
index 0000000..0c142a0
--- /dev/null
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextClearEvent.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.views.textinput;
+
+import androidx.annotation.Nullable;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.common.ViewUtil;
+import com.facebook.react.uimanager.events.Event;
+
+/**
+ * Event emitted by EditText native view when text changes. VisibleForTesting from {@link
+ * TextInputEventsTestCase}.
+ */
+public class ReactTextClearEvent extends Event<ReactTextClearEvent> {
+
+ public static final String EVENT_NAME = "topClear";
+
+ private String mText;
+ private int mEventCount;
+
+ @Deprecated
+ public ReactTextClearEvent(int viewId, String text, int eventCount) {
+ this(ViewUtil.NO_SURFACE_ID, viewId, text, eventCount);
+ }
+
+ public ReactTextClearEvent(int surfaceId, int viewId, String text, int eventCount) {
+ super(surfaceId, viewId);
+ mText = text;
+ mEventCount = eventCount;
+ }
+
+ @Override
+ public String getEventName() {
+ return EVENT_NAME;
+ }
+
+ @Nullable
+ @Override
+ protected WritableMap getEventData() {
+ WritableMap eventData = Arguments.createMap();
+ eventData.putString("text", mText);
+ eventData.putInt("eventCount", mEventCount);
+ eventData.putInt("target", getViewTag());
+ return eventData;
+ }
+}
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
index 8496a7d..53e5c49 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
@@ -8,6 +8,7 @@
package com.facebook.react.views.textinput;

import static com.facebook.react.uimanager.UIManagerHelper.getReactContext;
+import static com.facebook.react.uimanager.UIManagerHelper.getSurfaceId;

import android.content.Context;
import android.content.res.ColorStateList;
@@ -273,6 +274,9 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
.put(
ScrollEventType.getJSEventName(ScrollEventType.SCROLL),
MapBuilder.of("registrationName", "onScroll"))
+ .put(
+ ReactTextClearEvent.EVENT_NAME,
+ MapBuilder.of("registrationName", "onClear"))
.build());
return eventTypeConstants;
}
@@ -330,6 +334,27 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
reactEditText.maybeSetTextFromJS(getReactTextUpdate(text, mostRecentEventCount));
}
reactEditText.maybeSetSelection(mostRecentEventCount, start, end);
+ break;
+ case "clear":
+ // Capture the current text
+ Editable text = reactEditText.getText();
+
+ // Reset the edit text
+ ReactTextUpdate textUpdate = getReactTextUpdate("", reactEditText.incrementAndGetEventCounter());
+ reactEditText.maybeSetTextFromJS(textUpdate);
+ reactEditText.maybeSetSelection(reactEditText.incrementAndGetEventCounter(), 0, 0);
+
+ // Dispatch the clear event
+ EventDispatcher eventDispatcher = getEventDispatcher(getReactContext(reactEditText), reactEditText);
+ eventDispatcher.dispatchEvent(
+ new ReactTextClearEvent(
+ getSurfaceId(reactEditText),
+ reactEditText.getId(),
+ text.toString(),
+ reactEditText.incrementAndGetEventCounter()
+ )
+ );
+
break;
}
}
diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp
index 497569a..1c10b11 100644
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp
@@ -134,6 +134,11 @@ void TextInputEventEmitter::onBlur(
dispatchTextInputEvent("blur", textInputMetrics);
}

+void TextInputEventEmitter::onClear(
+ const TextInputMetrics& textInputMetrics) const {
+ dispatchTextInputEvent("clear", textInputMetrics);
+}
+
void TextInputEventEmitter::onChange(
const TextInputMetrics& textInputMetrics) const {
dispatchTextInputEvent("change", textInputMetrics);
diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h
index 0ab2b18..bc5e624 100644
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h
@@ -38,6 +38,7 @@ class TextInputEventEmitter : public ViewEventEmitter {

void onFocus(const TextInputMetrics& textInputMetrics) const;
void onBlur(const TextInputMetrics& textInputMetrics) const;
+ void onClear(const TextInputMetrics& textInputMetrics) const;
void onChange(const TextInputMetrics& textInputMetrics) const;
void onChangeSync(const TextInputMetrics& textInputMetrics) const;
void onContentSizeChange(const TextInputMetrics& textInputMetrics) const;
21 changes: 10 additions & 11 deletions src/components/Composer/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {MarkdownStyle} from '@expensify/react-native-live-markdown';
import type {ForwardedRef} from 'react';
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import type {TextInput} from 'react-native';
import React, {useCallback, useMemo, useRef} from 'react';
import type {NativeSyntheticEvent, TextInput, TextInputChangeEventData} from 'react-native';
import {StyleSheet} from 'react-native';
import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput';
import RNMarkdownTextInput from '@components/RNMarkdownTextInput';
Expand All @@ -19,8 +19,7 @@ const excludeReportMentionStyle: Array<keyof MarkdownStyle> = ['mentionReport'];

function Composer(
{
shouldClear = false,
onClear = () => {},
onClear: onClearProp = () => {},
isDisabled = false,
maxLines,
isComposerFullSize = false,
Expand Down Expand Up @@ -64,13 +63,12 @@ function Composer(
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (!shouldClear) {
return;
}
textInput.current?.clear();
onClear();
}, [shouldClear, onClear]);
const onClear = useCallback(
({nativeEvent}: NativeSyntheticEvent<TextInputChangeEventData>) => {
onClearProp(nativeEvent.text);
},
[onClearProp],
);

const maxHeightStyle = useMemo(() => StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize), [StyleUtils, isComposerFullSize, maxLines]);
const composerStyle = useMemo(() => StyleSheet.flatten([style, textContainsOnlyEmojis ? styles.onlyEmojisTextLineHeight : {}]), [style, textContainsOnlyEmojis, styles]);
Expand Down Expand Up @@ -99,6 +97,7 @@ function Composer(
}
props?.onBlur?.(e);
}}
onClear={onClear}
/>
);
}
Expand Down
Loading
Loading