Skip to content

Commit 92e0be2

Browse files
authored
Merge pull request #46091 from margelo/fix/37896-composer-not-cleared
Composer: add clear command that bypasses the event count
2 parents 75be218 + 28bafb4 commit 92e0be2

File tree

10 files changed

+438
-199
lines changed

10 files changed

+438
-199
lines changed

patches/react-native+0.73.4+023+iOS-fix-adjustFontSizeToFit-new-architecture.patch renamed to patches/react-native+0.73.4+022+iOS-fix-adjustFontSizeToFit-new-architecture.patch

File renamed without changes.

patches/react-native+0.73.4+022+textInputClear.patch

Lines changed: 0 additions & 66 deletions
This file was deleted.
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
2+
index 88d3cc8..8e60c9e 100644
3+
--- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
4+
+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
5+
@@ -97,6 +97,9 @@ const RCTTextInputViewConfig = {
6+
topChangeSync: {
7+
registrationName: 'onChangeSync',
8+
},
9+
+ topClear: {
10+
+ registrationName: 'onClear',
11+
+ },
12+
},
13+
validAttributes: {
14+
fontSize: true,
15+
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
16+
index 2c0c099..26a477f 100644
17+
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
18+
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
19+
@@ -707,6 +707,13 @@ export interface TextInputProps
20+
| ((e: NativeSyntheticEvent<TextInputFocusEventData>) => void)
21+
| undefined;
22+
23+
+ /**
24+
+ * Callback that is called when the text input was cleared using the native clear command.
25+
+ */
26+
+ onClear?:
27+
+ | ((e: NativeSyntheticEvent<TextInputChangeEventData>) => void)
28+
+ | undefined;
29+
+
30+
/**
31+
* Callback that is called when the text input's text changes.
32+
*/
33+
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
34+
index 481938f..346acaa 100644
35+
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
36+
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
37+
@@ -1329,6 +1329,11 @@ function InternalTextInput(props: Props): React.Node {
38+
});
39+
};
40+
41+
+ const _onClear = (event: ChangeEvent) => {
42+
+ setMostRecentEventCount(event.nativeEvent.eventCount);
43+
+ props.onClear && props.onClear(event);
44+
+ };
45+
+
46+
const _onFocus = (event: FocusEvent) => {
47+
TextInputState.focusInput(inputRef.current);
48+
if (props.onFocus) {
49+
@@ -1462,6 +1467,7 @@ function InternalTextInput(props: Props): React.Node {
50+
nativeID={id ?? props.nativeID}
51+
onBlur={_onBlur}
52+
onKeyPressSync={props.unstable_onKeyPressSync}
53+
+ onClear={_onClear}
54+
onChange={_onChange}
55+
onChangeSync={useOnChangeSync === true ? _onChangeSync : null}
56+
onContentSizeChange={props.onContentSizeChange}
57+
@@ -1516,6 +1522,7 @@ function InternalTextInput(props: Props): React.Node {
58+
nativeID={id ?? props.nativeID}
59+
numberOfLines={props.rows ?? props.numberOfLines}
60+
onBlur={_onBlur}
61+
+ onClear={_onClear}
62+
onChange={_onChange}
63+
onFocus={_onFocus}
64+
/* $FlowFixMe[prop-missing] the types for AndroidTextInput don't match
65+
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
66+
index a19b555..4785987 100644
67+
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
68+
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
69+
@@ -62,6 +62,7 @@ @implementation RCTBaseTextInputViewManager {
70+
71+
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
72+
RCT_EXPORT_VIEW_PROPERTY(onKeyPressSync, RCTDirectEventBlock)
73+
+RCT_EXPORT_VIEW_PROPERTY(onClear, RCTDirectEventBlock)
74+
RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock)
75+
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
76+
RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock)
77+
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
78+
index 7ce04da..70754bf 100644
79+
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
80+
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
81+
@@ -452,6 +452,19 @@ - (void)blur
82+
[_backedTextInputView resignFirstResponder];
83+
}
84+
85+
+- (void)clear
86+
+{
87+
+ auto metrics = [self _textInputMetrics];
88+
+ [self setTextAndSelection:_mostRecentEventCount value:@"" start:0 end:0];
89+
+
90+
+ _mostRecentEventCount++;
91+
+ metrics.eventCount = _mostRecentEventCount;
92+
+
93+
+ // Notify JS that the event counter has changed
94+
+ const auto &textInputEventEmitter = static_cast<const TextInputEventEmitter &>(*_eventEmitter);
95+
+ textInputEventEmitter.onClear(metrics);
96+
+}
97+
+
98+
- (void)setTextAndSelection:(NSInteger)eventCount
99+
value:(NSString *__nullable)value
100+
start:(NSInteger)start
101+
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
102+
index fe3376a..6889eed 100644
103+
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h
104+
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h
105+
@@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
106+
@protocol RCTTextInputViewProtocol <NSObject>
107+
- (void)focus;
108+
- (void)blur;
109+
+- (void)clear;
110+
- (void)setTextAndSelection:(NSInteger)eventCount
111+
value:(NSString *__nullable)value
112+
start:(NSInteger)start
113+
@@ -49,6 +50,19 @@ RCTTextInputHandleCommand(id<RCTTextInputViewProtocol> componentView, const NSSt
114+
return;
115+
}
116+
117+
+ if ([commandName isEqualToString:@"clear"]) {
118+
+#if RCT_DEBUG
119+
+ if ([args count] != 0) {
120+
+ RCTLogError(
121+
+ @"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0);
122+
+ return;
123+
+ }
124+
+#endif
125+
+
126+
+ [componentView clear];
127+
+ return;
128+
+ }
129+
+
130+
if ([commandName isEqualToString:@"setTextAndSelection"]) {
131+
#if RCT_DEBUG
132+
if ([args count] != 4) {
133+
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
134+
new file mode 100644
135+
index 0000000..0c142a0
136+
--- /dev/null
137+
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextClearEvent.java
138+
@@ -0,0 +1,53 @@
139+
+/*
140+
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
141+
+ *
142+
+ * This source code is licensed under the MIT license found in the
143+
+ * LICENSE file in the root directory of this source tree.
144+
+ */
145+
+
146+
+package com.facebook.react.views.textinput;
147+
+
148+
+import androidx.annotation.Nullable;
149+
+
150+
+import com.facebook.react.bridge.Arguments;
151+
+import com.facebook.react.bridge.WritableMap;
152+
+import com.facebook.react.uimanager.common.ViewUtil;
153+
+import com.facebook.react.uimanager.events.Event;
154+
+
155+
+/**
156+
+ * Event emitted by EditText native view when text changes. VisibleForTesting from {@link
157+
+ * TextInputEventsTestCase}.
158+
+ */
159+
+public class ReactTextClearEvent extends Event<ReactTextClearEvent> {
160+
+
161+
+ public static final String EVENT_NAME = "topClear";
162+
+
163+
+ private String mText;
164+
+ private int mEventCount;
165+
+
166+
+ @Deprecated
167+
+ public ReactTextClearEvent(int viewId, String text, int eventCount) {
168+
+ this(ViewUtil.NO_SURFACE_ID, viewId, text, eventCount);
169+
+ }
170+
+
171+
+ public ReactTextClearEvent(int surfaceId, int viewId, String text, int eventCount) {
172+
+ super(surfaceId, viewId);
173+
+ mText = text;
174+
+ mEventCount = eventCount;
175+
+ }
176+
+
177+
+ @Override
178+
+ public String getEventName() {
179+
+ return EVENT_NAME;
180+
+ }
181+
+
182+
+ @Nullable
183+
+ @Override
184+
+ protected WritableMap getEventData() {
185+
+ WritableMap eventData = Arguments.createMap();
186+
+ eventData.putString("text", mText);
187+
+ eventData.putInt("eventCount", mEventCount);
188+
+ eventData.putInt("target", getViewTag());
189+
+ return eventData;
190+
+ }
191+
+}
192+
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
193+
index 8496a7d..53e5c49 100644
194+
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
195+
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
196+
@@ -8,6 +8,7 @@
197+
package com.facebook.react.views.textinput;
198+
199+
import static com.facebook.react.uimanager.UIManagerHelper.getReactContext;
200+
+import static com.facebook.react.uimanager.UIManagerHelper.getSurfaceId;
201+
202+
import android.content.Context;
203+
import android.content.res.ColorStateList;
204+
@@ -273,6 +274,9 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
205+
.put(
206+
ScrollEventType.getJSEventName(ScrollEventType.SCROLL),
207+
MapBuilder.of("registrationName", "onScroll"))
208+
+ .put(
209+
+ ReactTextClearEvent.EVENT_NAME,
210+
+ MapBuilder.of("registrationName", "onClear"))
211+
.build());
212+
return eventTypeConstants;
213+
}
214+
@@ -330,6 +334,27 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
215+
reactEditText.maybeSetTextFromJS(getReactTextUpdate(text, mostRecentEventCount));
216+
}
217+
reactEditText.maybeSetSelection(mostRecentEventCount, start, end);
218+
+ break;
219+
+ case "clear":
220+
+ // Capture the current text
221+
+ Editable text = reactEditText.getText();
222+
+
223+
+ // Reset the edit text
224+
+ ReactTextUpdate textUpdate = getReactTextUpdate("", reactEditText.incrementAndGetEventCounter());
225+
+ reactEditText.maybeSetTextFromJS(textUpdate);
226+
+ reactEditText.maybeSetSelection(reactEditText.incrementAndGetEventCounter(), 0, 0);
227+
+
228+
+ // Dispatch the clear event
229+
+ EventDispatcher eventDispatcher = getEventDispatcher(getReactContext(reactEditText), reactEditText);
230+
+ eventDispatcher.dispatchEvent(
231+
+ new ReactTextClearEvent(
232+
+ getSurfaceId(reactEditText),
233+
+ reactEditText.getId(),
234+
+ text.toString(),
235+
+ reactEditText.incrementAndGetEventCounter()
236+
+ )
237+
+ );
238+
+
239+
break;
240+
}
241+
}
242+
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
243+
index 497569a..1c10b11 100644
244+
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp
245+
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp
246+
@@ -134,6 +134,11 @@ void TextInputEventEmitter::onBlur(
247+
dispatchTextInputEvent("blur", textInputMetrics);
248+
}
249+
250+
+void TextInputEventEmitter::onClear(
251+
+ const TextInputMetrics& textInputMetrics) const {
252+
+ dispatchTextInputEvent("clear", textInputMetrics);
253+
+}
254+
+
255+
void TextInputEventEmitter::onChange(
256+
const TextInputMetrics& textInputMetrics) const {
257+
dispatchTextInputEvent("change", textInputMetrics);
258+
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
259+
index 0ab2b18..bc5e624 100644
260+
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h
261+
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h
262+
@@ -38,6 +38,7 @@ class TextInputEventEmitter : public ViewEventEmitter {
263+
264+
void onFocus(const TextInputMetrics& textInputMetrics) const;
265+
void onBlur(const TextInputMetrics& textInputMetrics) const;
266+
+ void onClear(const TextInputMetrics& textInputMetrics) const;
267+
void onChange(const TextInputMetrics& textInputMetrics) const;
268+
void onChangeSync(const TextInputMetrics& textInputMetrics) const;
269+
void onContentSizeChange(const TextInputMetrics& textInputMetrics) const;

src/components/Composer/index.native.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {MarkdownStyle} from '@expensify/react-native-live-markdown';
22
import type {ForwardedRef} from 'react';
3-
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
4-
import type {TextInput} from 'react-native';
3+
import React, {useCallback, useMemo, useRef} from 'react';
4+
import type {NativeSyntheticEvent, TextInput, TextInputChangeEventData} from 'react-native';
55
import {StyleSheet} from 'react-native';
66
import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput';
77
import RNMarkdownTextInput from '@components/RNMarkdownTextInput';
@@ -19,8 +19,7 @@ const excludeReportMentionStyle: Array<keyof MarkdownStyle> = ['mentionReport'];
1919

2020
function Composer(
2121
{
22-
shouldClear = false,
23-
onClear = () => {},
22+
onClear: onClearProp = () => {},
2423
isDisabled = false,
2524
maxLines,
2625
isComposerFullSize = false,
@@ -64,13 +63,12 @@ function Composer(
6463
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
6564
}, []);
6665

67-
useEffect(() => {
68-
if (!shouldClear) {
69-
return;
70-
}
71-
textInput.current?.clear();
72-
onClear();
73-
}, [shouldClear, onClear]);
66+
const onClear = useCallback(
67+
({nativeEvent}: NativeSyntheticEvent<TextInputChangeEventData>) => {
68+
onClearProp(nativeEvent.text);
69+
},
70+
[onClearProp],
71+
);
7472

7573
const maxHeightStyle = useMemo(() => StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize), [StyleUtils, isComposerFullSize, maxLines]);
7674
const composerStyle = useMemo(
@@ -102,6 +100,7 @@ function Composer(
102100
}
103101
props?.onBlur?.(e);
104102
}}
103+
onClear={onClear}
105104
/>
106105
);
107106
}

0 commit comments

Comments
 (0)