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

Add onPaste to TextInput #45425

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,11 @@ export type NativeProps = $ReadOnly<{|
|}>,
>,

/**
* Callback that is called when the clipboard content is pasted.
*/
onPaste?: ?DirectEventHandler<$ReadOnly<{|target: Int32|}>>,

/**
* The string that will be rendered before text input has been entered.
*/
Expand Down Expand Up @@ -658,6 +663,9 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
topScroll: {
registrationName: 'onScroll',
},
topPaste: {
registrationName: 'onPaste',
},
},
validAttributes: {
maxFontSizeMultiplier: true,
Expand Down Expand Up @@ -712,6 +720,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
textBreakStrategy: true,
onScroll: true,
onContentSizeChange: true,
onPaste: true,
disableFullscreenUI: true,
includeFontPadding: true,
fontWeight: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ const RCTTextInputViewConfig = {
topContentSizeChange: {
registrationName: 'onContentSizeChange',
},
topPaste: {
registrationName: 'onPaste',
},
},
validAttributes: {
fontSize: true,
Expand Down Expand Up @@ -150,6 +153,7 @@ const RCTTextInputViewConfig = {
onSelectionChange: true,
onContentSizeChange: true,
onScroll: true,
onPaste: true,
}),
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,11 @@ export interface TextInputProps
| ((e: NativeSyntheticEvent<TextInputKeyPressEventData>) => void)
| undefined;

/**
* Callback that is called when the clipboard content is pasted.
*/
onPaste?: ((e: NativeSyntheticEvent<TargetedEvent>) => void) | undefined;

/**
* The string that will be rendered before text input has been entered
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,11 @@ export type Props = $ReadOnly<{|
*/
onScroll?: ?(e: ScrollEvent) => mixed,

/**
* Callback that is called when the clipboard content is pasted.
*/
onPaste?: ?(e: TargetEvent) => mixed,

/**
* The string that will be rendered before text input has been entered.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,11 @@
*/
onScroll?: ?(e: ScrollEvent) => mixed,

/**
* Callback that is called when the clipboard content is pasted.
*/
onPaste?: ?(e: TargetEvent) => mixed,

/**
* The string that will be rendered before text input has been entered.
*/
Expand Down Expand Up @@ -1270,7 +1275,7 @@

const inputRef = useRef<null | React.ElementRef<HostComponent<mixed>>>(null);

// eslint-disable-next-line react-hooks/exhaustive-deps

Check warning on line 1278 in packages/react-native/Libraries/Components/TextInput/TextInput.js

View workflow job for this annotation

GitHub Actions / test_js (20)

'react-hooks/exhaustive-deps' rule is disabled but never reported

Check warning on line 1278 in packages/react-native/Libraries/Components/TextInput/TextInput.js

View workflow job for this annotation

GitHub Actions / test_js (18)

'react-hooks/exhaustive-deps' rule is disabled but never reported
const selection: ?Selection =
propsSelection == null
? null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ - (void)paste:(id)sender
{
_textWasPasted = YES;
[super paste:sender];
[_textInputDelegateAdapter didPaste];
}

// Turn off scroll animation to fix flaky scrolling.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)textInputDidChange;

- (void)textInputDidChangeSelection;
- (void)textInputDidPaste;

@optional

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN

- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
- (void)selectedTextRangeWasSet;
- (void)didPaste;

@end

Expand All @@ -30,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView;

- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
- (void)didPaste;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ - (void)selectedTextRangeWasSet
[self textFieldProbablyDidChangeSelection];
}

- (void)didPaste
{
[_backedTextInputView.textInputDelegate textInputDidPaste];
}

#pragma mark - Generalization

- (void)textFieldProbablyDidChangeSelection
Expand Down Expand Up @@ -292,6 +297,11 @@ - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)tex
_previousSelectedTextRange = textRange;
}

- (void)didPaste
{
[_backedTextInputView.textInputDelegate textInputDidPaste];
}

#pragma mark - Generalization

- (void)textViewProbablyDidChangeSelection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChangeSync;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onScroll;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onPaste;

@property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, assign, readonly) NSInteger nativeEventCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,14 @@ - (void)textInputDidChangeSelection
});
}

- (void)textInputDidPaste
{
if (!_onPaste) {
return;
}
_onPaste(@{@"target" : self.reactTag});
}

- (void)updateLocalData
{
[self enforceTextAttributesIfNeeded];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ @implementation RCTBaseTextInputViewManager {
RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPaste, RCTDirectEventBlock)

RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ - (void)paste:(id)sender
{
_textWasPasted = YES;
[super paste:sender];
[_textInputDelegateAdapter didPaste];
}

#pragma mark - Layout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,13 @@ - (void)textInputDidChangeSelection
}
}

- (void)textInputDidPaste
{
if (_eventEmitter) {
static_cast<const TextInputEventEmitter &>(*_eventEmitter).onPaste();
}
}

#pragma mark - RCTBackedTextInputDelegate (UIScrollViewDelegate)

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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;

/**
* Implement this interface to be informed of paste event in the
* ReactTextEdit This is used by the ReactTextInputManager to forward events
* from the EditText to JS
*/
interface PasteWatcher {
public void onPaste();
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public class ReactEditText extends AppCompatEditText {
private @Nullable SelectionWatcher mSelectionWatcher;
private @Nullable ContentSizeWatcher mContentSizeWatcher;
private @Nullable ScrollWatcher mScrollWatcher;
private @Nullable PasteWatcher mPasteWatcher;
private InternalKeyListener mKeyListener;
private boolean mDetectScrollMovement = false;
private boolean mOnKeyPress = false;
Expand Down Expand Up @@ -154,6 +155,7 @@ public ReactEditText(Context context) {
mKeyListener = new InternalKeyListener();
}
mScrollWatcher = null;
mPasteWatcher = null;
mTextAttributes = new TextAttributes();

applyTextAttributes();
Expand Down Expand Up @@ -321,8 +323,13 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
*/
@Override
public boolean onTextContextMenuItem(int id) {
if (id == android.R.id.paste) {
if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {
id = android.R.id.pasteAsPlainText;
boolean actionPerformed = super.onTextContextMenuItem(id);
if (mPasteWatcher != null) {
mPasteWatcher.onPaste();
}
return actionPerformed;
}
return super.onTextContextMenuItem(id);
}
Expand Down Expand Up @@ -384,6 +391,10 @@ public void setScrollWatcher(@Nullable ScrollWatcher scrollWatcher) {
mScrollWatcher = scrollWatcher;
}

public void setPasteWatcher(@Nullable PasteWatcher pasteWatcher) {
mPasteWatcher = pasteWatcher;
}

/**
* Attempt to set a selection or fail silently. Intentionally meant to handle bad inputs.
* EventCounter is the same one used as with text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
.put(
ScrollEventType.getJSEventName(ScrollEventType.SCROLL),
MapBuilder.of("registrationName", "onScroll"))
.put(
"topPaste",
MapBuilder.of("registrationName", "onPaste"))
.build());
return eventTypeConstants;
}
Expand Down Expand Up @@ -476,6 +479,15 @@ public void setOnScroll(final ReactEditText view, boolean onScroll) {
}
}

@ReactProp(name = "onPaste", defaultBoolean = false)
public void setOnPaste(final ReactEditText view, boolean onPaste) {
if (onPaste) {
view.setPasteWatcher(new ReactPasteWatcher(view));
} else {
view.setPasteWatcher(null);
}
}

@ReactProp(name = "onKeyPress", defaultBoolean = false)
public void setOnKeyPress(final ReactEditText view, boolean onKeyPress) {
view.setOnKeyPress(onKeyPress);
Expand Down Expand Up @@ -1307,6 +1319,25 @@ public void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
}
}

private static class ReactPasteWatcher implements PasteWatcher {
private final ReactEditText mReactEditText;
private final EventDispatcher mEventDispatcher;
private final int mSurfaceId;

public ReactPasteWatcher(ReactEditText editText) {
mReactEditText = editText;
ReactContext reactContext = getReactContext(editText);
mEventDispatcher = getEventDispatcher(reactContext, editText);
mSurfaceId = UIManagerHelper.getSurfaceId(reactContext);
}

@Override
public void onPaste() {
mEventDispatcher.dispatchEvent(
new ReactTextInputPasteEvent(mSurfaceId, mReactEditText.getId()));
}
}

@Override
public @Nullable Map<String, Object> getExportedViewConstants() {
return MapBuilder.of(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.events.Event;

/**
* Event emitted by EditText native view when clipboard content is pasted
*/
class ReactTextInputPasteEvent extends Event<ReactTextInputPasteEvent> {

private static final String EVENT_NAME = "topPaste";

@Deprecated
public ReactTextInputPasteEvent(int viewId) {
this(ViewUtil.NO_SURFACE_ID, viewId);
}

public ReactTextInputPasteEvent(int surfaceId, int viewId) {
super(surfaceId, viewId);
}

@Override
public String getEventName() {
return EVENT_NAME;
}

@Override
public boolean canCoalesce() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ void TextInputEventEmitter::onScroll(const Metrics& textInputMetrics) const {
});
}

void TextInputEventEmitter::onPaste() const {
dispatchEvent("onPaste");
}

void TextInputEventEmitter::dispatchTextInputEvent(
const std::string& name,
const Metrics& textInputMetrics) const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class TextInputEventEmitter : public ViewEventEmitter {
void onSubmitEditing(const Metrics& textInputMetrics) const;
void onKeyPress(const KeyPressMetrics& keyPressMetrics) const;
void onScroll(const Metrics& textInputMetrics) const;
void onPaste() const;

private:
void dispatchTextInputEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ class TextEventsExample extends React.Component<{...}, $FlowFixMeState> {
onKeyPress={event =>
this.updateText('onKeyPress key: ' + event.nativeEvent.key)
}
onPaste={() => this.updateText('onPaste')}
style={styles.singleLine}
/>
<Text style={styles.eventLabel}>
Expand Down
Loading