1315 lines
53 KiB
Diff
1315 lines
53 KiB
Diff
diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
|
|
index 8cc1369..6d5e45e 100644
|
|
--- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
|
|
+++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
|
|
@@ -449,6 +449,21 @@ export type AndroidTextInputNativeProps = $ReadOnly<{
|
|
}>,
|
|
>,
|
|
|
|
+ /**
|
|
+ * Invoked when the user performs the paste action.
|
|
+ */
|
|
+ onPaste?: ?DirectEventHandler<
|
|
+ $ReadOnly<{|
|
|
+ target: Int32,
|
|
+ items: $ReadOnlyArray<
|
|
+ $ReadOnly<{|
|
|
+ type: string,
|
|
+ data: string,
|
|
+ |}>,
|
|
+ >,
|
|
+ |}>,
|
|
+ >,
|
|
+
|
|
/**
|
|
* The string that will be rendered before text input has been entered.
|
|
*/
|
|
@@ -640,6 +655,9 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
|
|
topScroll: {
|
|
registrationName: 'onScroll',
|
|
},
|
|
+ topPaste: {
|
|
+ registrationName: 'onPaste',
|
|
+ },
|
|
},
|
|
validAttributes: {
|
|
acceptDragAndDropTypes: true,
|
|
@@ -695,6 +713,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
|
|
textBreakStrategy: true,
|
|
onScroll: true,
|
|
onContentSizeChange: true,
|
|
+ onPaste: true,
|
|
disableFullscreenUI: true,
|
|
includeFontPadding: true,
|
|
fontWeight: true,
|
|
diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
|
|
index 05bddcb..69c11a2 100644
|
|
--- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
|
|
+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js
|
|
@@ -82,6 +82,9 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
|
|
topContentSizeChange: {
|
|
registrationName: 'onContentSizeChange',
|
|
},
|
|
+ topPaste: {
|
|
+ registrationName: 'onPaste',
|
|
+ },
|
|
topChangeSync: {
|
|
registrationName: 'onChangeSync',
|
|
},
|
|
@@ -160,6 +163,7 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = {
|
|
onSelectionChange: true,
|
|
onContentSizeChange: true,
|
|
onScroll: true,
|
|
+ onPaste: true,
|
|
onChangeSync: true,
|
|
onKeyPressSync: 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 b3ca156..36504b5 100644
|
|
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
|
|
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts
|
|
@@ -565,6 +565,22 @@ export interface TextInputSubmitEditingEventData {
|
|
export type TextInputSubmitEditingEvent =
|
|
NativeSyntheticEvent<TextInputSubmitEditingEventData>;
|
|
|
|
+/**
|
|
+ * @deprecated Use `TextInputPasteEvent` instead
|
|
+ */
|
|
+export interface TextInputPasteEventData extends TargetedEvent {
|
|
+ items: Array<{
|
|
+ type: string;
|
|
+ data: string;
|
|
+ }>;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @see TextInputProps.onPaste
|
|
+ */
|
|
+export type TextInputPasteEvent =
|
|
+ NativeSyntheticEvent<TextInputPasteEventData>;
|
|
+
|
|
/**
|
|
* @see https://reactnative.dev/docs/textinput#props
|
|
*/
|
|
@@ -896,6 +912,11 @@ export interface TextInputProps
|
|
*/
|
|
onKeyPress?: ((e: TextInputKeyPressEvent) => void) | undefined;
|
|
|
|
+ /**
|
|
+ * Invoked when the user performs the paste action.
|
|
+ */
|
|
+ onPaste?: ((e: TextInputPasteEvent) => void) | undefined;
|
|
+
|
|
/**
|
|
* The string that will be rendered before text input has been entered
|
|
*/
|
|
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
|
|
index 5fa8811..7eb26a7 100644
|
|
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
|
|
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js
|
|
@@ -137,6 +137,22 @@ export type TextInputSubmitEditingEvent =
|
|
export type TextInputEditingEvent =
|
|
NativeSyntheticEvent<TextInputEndEditingEventData>;
|
|
|
|
+type TextInputPasteEventData = $ReadOnly<{|
|
|
+ target: number,
|
|
+ items: $ReadOnlyArray<
|
|
+ $ReadOnly<{|
|
|
+ type: string,
|
|
+ data: string,
|
|
+ |}>,
|
|
+ >,
|
|
+|}>
|
|
+
|
|
+/**
|
|
+ * @see TextInputProps.onPaste
|
|
+ */
|
|
+export type TextInputPasteEvent =
|
|
+ NativeSyntheticEvent<TextInputPasteEventData>;
|
|
+
|
|
type DataDetectorTypesType =
|
|
| 'phoneNumber'
|
|
| 'link'
|
|
@@ -884,6 +900,11 @@ type TextInputBaseProps = $ReadOnly<{
|
|
*/
|
|
onScroll?: ?(e: ScrollEvent) => mixed,
|
|
|
|
+ /**
|
|
+ * Invoked when the user performs the paste action.
|
|
+ */
|
|
+ onPaste?: ?(e: TextInputPasteEvent) => mixed,
|
|
+
|
|
/**
|
|
* The string that will be rendered before text input has been entered.
|
|
*/
|
|
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
|
|
index 1e95eb6..bb489db 100644
|
|
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
|
|
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
|
|
@@ -45,6 +45,7 @@ import type {
|
|
TextInputInstance,
|
|
TextInputIOSProps,
|
|
TextInputKeyPressEvent,
|
|
+ TextInputPasteEvent,
|
|
TextInputProps,
|
|
TextInputSelectionChangeEvent,
|
|
TextInputSubmitEditingEvent,
|
|
@@ -115,6 +116,7 @@ export type {
|
|
TextInputFocusEvent,
|
|
TextInputIOSProps,
|
|
TextInputKeyPressEvent,
|
|
+ TextInputPasteEvent,
|
|
TextInputProps,
|
|
TextInputSelectionChangeEvent,
|
|
TextInputSubmitEditingEvent,
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm b/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm
|
|
index 6e9c384..17d157d 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm
|
|
@@ -13,6 +13,10 @@
|
|
#import <React/RCTBackedTextInputDelegateAdapter.h>
|
|
#import <React/RCTTextAttributes.h>
|
|
|
|
+#import <MobileCoreServices/MobileCoreServices.h>
|
|
+#import <MobileCoreServices/UTType.h>
|
|
+#import <UIKit/UIKit.h>
|
|
+
|
|
@implementation RCTUITextView {
|
|
UILabel *_placeholderView;
|
|
UITextView *_detachedTextView;
|
|
@@ -209,7 +213,32 @@ - (void)scrollRangeToVisible:(NSRange)range
|
|
- (void)paste:(id)sender
|
|
{
|
|
_textWasPasted = YES;
|
|
- [super paste:sender];
|
|
+ UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
|
|
+ if (clipboard.hasImages) {
|
|
+ for (NSItemProvider *itemProvider in clipboard.itemProviders) {
|
|
+ if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
|
|
+ for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
|
|
+ if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeImage)) {
|
|
+ NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassMIMEType);
|
|
+ NSString *fileExtension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassFilenameExtension);
|
|
+ NSString *fileName = [NSString stringWithFormat:@"%@.%@", [[NSUUID UUID] UUIDString], fileExtension];
|
|
+ NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
|
|
+ NSURL *fileURL = [NSURL fileURLWithPath:filePath];
|
|
+ NSData *fileData = [clipboard dataForPasteboardType:identifier];
|
|
+ [fileData writeToFile:filePath atomically:YES];
|
|
+ [_textInputDelegateAdapter didPaste:MIMEType withData:[fileURL absoluteString]];
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ if (clipboard.hasStrings) {
|
|
+ [_textInputDelegateAdapter didPaste:@"text/plain" withData:clipboard.string];
|
|
+ }
|
|
+ [super paste:sender];
|
|
+ }
|
|
}
|
|
|
|
// Turn off scroll animation to fix flaky scrolling.
|
|
@@ -301,6 +330,10 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
|
return NO;
|
|
}
|
|
|
|
+ if (action == @selector(paste:) && [UIPasteboard generalPasteboard].hasImages) {
|
|
+ return YES;
|
|
+ }
|
|
+
|
|
return [super canPerformAction:action withSender:sender];
|
|
}
|
|
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
|
|
index 7187177..748c4cc 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
|
|
@@ -36,6 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
- (void)textInputDidChange;
|
|
|
|
- (void)textInputDidChangeSelection;
|
|
+- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data;
|
|
|
|
@optional
|
|
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
|
|
index f1c32e6..0ce9dfe 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h
|
|
@@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
|
|
- (void)selectedTextRangeWasSet;
|
|
+- (void)didPaste:(NSString *)type withData:(NSString *)data;
|
|
|
|
@end
|
|
|
|
@@ -30,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView;
|
|
|
|
- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
|
|
+- (void)didPaste:(NSString *)type withData:(NSString *)data;
|
|
|
|
@end
|
|
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
|
|
index 82d9a79..8cc48ec 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm
|
|
@@ -148,6 +148,11 @@ - (void)selectedTextRangeWasSet
|
|
[self textFieldProbablyDidChangeSelection];
|
|
}
|
|
|
|
+- (void)didPaste:(NSString *)type withData:(NSString *)data
|
|
+{
|
|
+ [_backedTextInputView.textInputDelegate textInputDidPaste:type withData:data];
|
|
+}
|
|
+
|
|
#pragma mark - Generalization
|
|
|
|
- (void)textFieldProbablyDidChangeSelection
|
|
@@ -330,6 +335,11 @@ - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)tex
|
|
_previousSelectedTextRange = textRange;
|
|
}
|
|
|
|
+- (void)didPaste:(NSString *)type withData:(NSString *)data
|
|
+{
|
|
+ [_backedTextInputView.textInputDelegate textInputDidPaste:type withData:data];
|
|
+}
|
|
+
|
|
#pragma mark - Generalization
|
|
|
|
- (void)textViewProbablyDidChangeSelection
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
|
|
index 4804624..90b7081 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h
|
|
@@ -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;
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
|
|
index 6a2d4f8..b6e6060 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
|
|
@@ -599,6 +599,26 @@ - (void)textInputDidChangeSelection
|
|
});
|
|
}
|
|
|
|
+- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data
|
|
+{
|
|
+ if (!_onPaste) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ NSMutableArray *items = [NSMutableArray new];
|
|
+ [items addObject:@{
|
|
+ @"type" : type,
|
|
+ @"data" : data,
|
|
+ }];
|
|
+
|
|
+ NSDictionary *payload = @{
|
|
+ @"target" : self.reactTag,
|
|
+ @"items" : items,
|
|
+ };
|
|
+
|
|
+ _onPaste(payload);
|
|
+}
|
|
+
|
|
- (void)updateLocalData
|
|
{
|
|
[self enforceTextAttributesIfNeeded];
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
|
|
index 47adc53..1865e0a 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm
|
|
@@ -70,6 +70,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_SHADOW_PROPERTY(text, NSString)
|
|
RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
|
|
diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
|
|
index 377f41e..be99a7c 100644
|
|
--- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
|
|
+++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
|
|
@@ -12,6 +12,10 @@
|
|
#import <React/RCTUtils.h>
|
|
#import <React/UIView+React.h>
|
|
|
|
+#import <MobileCoreServices/MobileCoreServices.h>
|
|
+#import <MobileCoreServices/UTType.h>
|
|
+#import <UIKit/UIKit.h>
|
|
+
|
|
@implementation RCTUITextField {
|
|
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
|
|
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
|
|
@@ -180,6 +184,10 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
|
return NO;
|
|
}
|
|
|
|
+ if (action == @selector(paste:) && [UIPasteboard generalPasteboard].hasImages) {
|
|
+ return YES;
|
|
+ }
|
|
+
|
|
return [super canPerformAction:action withSender:sender];
|
|
}
|
|
|
|
@@ -263,7 +271,32 @@ - (void)scrollRangeToVisible:(NSRange)range
|
|
- (void)paste:(id)sender
|
|
{
|
|
_textWasPasted = YES;
|
|
- [super paste:sender];
|
|
+ UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
|
|
+ if (clipboard.hasImages) {
|
|
+ for (NSItemProvider *itemProvider in clipboard.itemProviders) {
|
|
+ if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
|
|
+ for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
|
|
+ if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeImage)) {
|
|
+ NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassMIMEType);
|
|
+ NSString *fileExtension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassFilenameExtension);
|
|
+ NSString *fileName = [NSString stringWithFormat:@"%@.%@", [[NSUUID UUID] UUIDString], fileExtension];
|
|
+ NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
|
|
+ NSURL *fileURL = [NSURL fileURLWithPath:filePath];
|
|
+ NSData *fileData = [clipboard dataForPasteboardType:identifier];
|
|
+ [fileData writeToFile:filePath atomically:YES];
|
|
+ [_textInputDelegateAdapter didPaste:MIMEType withData:[fileURL absoluteString]];
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ if (clipboard.hasStrings) {
|
|
+ [_textInputDelegateAdapter didPaste:@"text/plain" withData:clipboard.string];
|
|
+ }
|
|
+ [super paste:sender];
|
|
+ }
|
|
}
|
|
|
|
#pragma mark - Layout
|
|
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
|
|
index 914a249..0deac55 100644
|
|
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
|
|
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
|
|
@@ -19,6 +19,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
*/
|
|
@interface RCTPullToRefreshViewComponentView : RCTViewComponentView <RCTCustomPullToRefreshViewProtocol>
|
|
|
|
+- (void)beginRefreshingProgrammatically;
|
|
+
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|
|
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
|
index 1494fd2..df643f5 100644
|
|
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
|
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
|
@@ -1038,6 +1038,11 @@ - (void)_adjustForMaintainVisibleContentPosition
|
|
}
|
|
}
|
|
|
|
++ (BOOL)shouldBeRecycled
|
|
+{
|
|
+ return NO;
|
|
+}
|
|
+
|
|
@end
|
|
|
|
Class<RCTComponentViewProtocol> RCTScrollViewCls(void)
|
|
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 577bebe..bbde7fd 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
|
|
@@ -516,6 +516,13 @@ - (void)textInputDidChangeSelection
|
|
}
|
|
}
|
|
|
|
+- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data
|
|
+{
|
|
+ if (_eventEmitter) {
|
|
+ static_cast<const TextInputEventEmitter &>(*_eventEmitter).onPaste(std::string([type UTF8String]), std::string([data UTF8String]));
|
|
+ }
|
|
+}
|
|
+
|
|
#pragma mark - RCTBackedTextInputDelegate (UIScrollViewDelegate)
|
|
|
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
|
index e9b330f..ec5f58c 100644
|
|
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
|
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
|
|
@@ -15,5 +15,8 @@
|
|
@property (nonatomic, copy) NSString *title;
|
|
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
|
|
@property (nonatomic, weak) UIScrollView *scrollView;
|
|
+@property (nonatomic, copy) UIColor *customTintColor;
|
|
+
|
|
+- (void)forwarderBeginRefreshing;
|
|
|
|
@end
|
|
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
|
index 53bfd04..ff1b1ed 100644
|
|
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
|
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
|
|
@@ -23,6 +23,7 @@ @implementation RCTRefreshControl {
|
|
UIColor *_titleColor;
|
|
CGFloat _progressViewOffset;
|
|
BOOL _hasMovedToWindow;
|
|
+ UIColor *_customTintColor;
|
|
}
|
|
|
|
- (instancetype)init
|
|
@@ -58,6 +59,12 @@ - (void)layoutSubviews
|
|
_isInitialRender = false;
|
|
}
|
|
|
|
+- (void)didMoveToSuperview
|
|
+{
|
|
+ [super didMoveToSuperview];
|
|
+ [self setTintColor:_customTintColor];
|
|
+}
|
|
+
|
|
- (void)didMoveToWindow
|
|
{
|
|
[super didMoveToWindow];
|
|
@@ -221,4 +228,50 @@ - (void)refreshControlValueChanged
|
|
}
|
|
}
|
|
|
|
+// Fix for https://github.com/facebook/react-native/issues/43388
|
|
+// A bug in iOS 17.4 causes the haptic to not play when refreshing if the tintColor
|
|
+// is set before the refresh control gets added to the scrollview. We'll call this
|
|
+// function whenever the superview changes. We'll also call it if the value of customTintColor
|
|
+// changes.
|
|
+- (void)setTintColor:(UIColor *)tintColor
|
|
+{
|
|
+ if ([self.superview isKindOfClass:[UIScrollView class]] && self.tintColor != tintColor) {
|
|
+ [super setTintColor:tintColor];
|
|
+ }
|
|
+}
|
|
+
|
|
+// This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native
|
|
+// libraries to perform a refresh of a scrollview and access the refresh control's onRefresh
|
|
+// function.
|
|
+- (void)forwarderBeginRefreshing
|
|
+{
|
|
+ _refreshingProgrammatically = NO;
|
|
+
|
|
+ [self sizeToFit];
|
|
+
|
|
+ if (!self.scrollView) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
|
|
+
|
|
+ [UIView animateWithDuration:0.3
|
|
+ delay:0
|
|
+ options:UIViewAnimationOptionBeginFromCurrentState
|
|
+ animations:^(void) {
|
|
+ // Whenever we call this method, the scrollview will always be at a position of
|
|
+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
|
|
+ [scrollView setContentOffset:CGPointMake(0, -65)];
|
|
+ }
|
|
+ completion:^(__unused BOOL finished) {
|
|
+ [super beginRefreshing];
|
|
+ [self setCurrentRefreshingState:super.refreshing];
|
|
+
|
|
+ if (self->_onRefresh) {
|
|
+ self->_onRefresh(nil);
|
|
+ }
|
|
+ }
|
|
+ ];
|
|
+}
|
|
+
|
|
@end
|
|
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
|
index 40aaf9c..1c60164 100644
|
|
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
|
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
|
|
@@ -22,11 +22,12 @@ - (UIView *)view
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock)
|
|
RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL)
|
|
-RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
|
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
|
|
RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
|
|
RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat)
|
|
|
|
+RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor)
|
|
+
|
|
RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing)
|
|
{
|
|
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt
|
|
new file mode 100644
|
|
index 0000000..95e45ce
|
|
--- /dev/null
|
|
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt
|
|
@@ -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 events in the
|
|
+ * ReactTextEdit. This is used by the ReactTextInputManager to forward events
|
|
+ * from the EditText to JS.
|
|
+ */
|
|
+interface PasteWatcher {
|
|
+ fun onPaste(type: String, data: String)
|
|
+}
|
|
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt
|
|
index 42f6e03..86f624f 100644
|
|
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt
|
|
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt
|
|
@@ -7,6 +7,10 @@
|
|
|
|
package com.facebook.react.views.textinput
|
|
|
|
+import android.content.ClipboardManager
|
|
+import android.content.ClipData
|
|
+import android.content.ClipDescription
|
|
+import android.content.ContentResolver
|
|
import android.annotation.SuppressLint
|
|
import android.content.Context
|
|
import android.content.res.Configuration
|
|
@@ -15,6 +19,7 @@ import android.graphics.Color
|
|
import android.graphics.Paint
|
|
import android.graphics.Rect
|
|
import android.graphics.drawable.Drawable
|
|
+import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.text.Editable
|
|
@@ -128,6 +133,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
private var selectionWatcher: SelectionWatcher? = null
|
|
private var contentSizeWatcher: ContentSizeWatcher? = null
|
|
private var scrollWatcher: ScrollWatcher?
|
|
+ private var pasteWatcher: PasteWatcher?
|
|
private var keyListener: InternalKeyListener? = null
|
|
private var detectScrollMovement = false
|
|
private var onKeyPress = false
|
|
@@ -162,9 +168,9 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
private val isSecureText: Boolean
|
|
get() =
|
|
- ((inputType and
|
|
- (InputType.TYPE_NUMBER_VARIATION_PASSWORD or InputType.TYPE_TEXT_VARIATION_PASSWORD)) !=
|
|
- 0)
|
|
+ ((inputType and
|
|
+ (InputType.TYPE_NUMBER_VARIATION_PASSWORD or
|
|
+ InputType.TYPE_TEXT_VARIATION_PASSWORD)) != 0)
|
|
|
|
public var disableFullscreenUI: Boolean
|
|
get() = disableFullscreen
|
|
@@ -181,13 +187,14 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
internal var gravityHorizontal: Int
|
|
get() =
|
|
- (gravity and (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK))
|
|
+ (gravity and
|
|
+ (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK))
|
|
set(value) {
|
|
val newValue = if (value == 0) defaultGravityHorizontal else value
|
|
gravity =
|
|
- ((gravity and
|
|
- Gravity.HORIZONTAL_GRAVITY_MASK.inv() and
|
|
- Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK.inv()) or newValue)
|
|
+ ((gravity and
|
|
+ Gravity.HORIZONTAL_GRAVITY_MASK.inv() and
|
|
+ Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK.inv()) or newValue)
|
|
}
|
|
|
|
internal var gravityVertical: Int
|
|
@@ -199,9 +206,10 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
init {
|
|
inputMethodManager =
|
|
- context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
+ context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
defaultGravityHorizontal =
|
|
- gravity and (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
|
|
+ gravity and
|
|
+ (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
|
|
defaultGravityVertical = gravity and Gravity.VERTICAL_GRAVITY_MASK
|
|
nativeEventCount = 0
|
|
isSettingTextFromJS = false
|
|
@@ -219,49 +227,61 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
// Turn off hardware acceleration for Oreo (T40484798)
|
|
// see https://issuetracker.google.com/issues/67102093
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
|
- Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
|
|
+ Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1
|
|
+ ) {
|
|
setLayerType(LAYER_TYPE_SOFTWARE, null)
|
|
}
|
|
|
|
val editTextAccessibilityDelegate: ReactAccessibilityDelegate =
|
|
- object :
|
|
- ReactAccessibilityDelegate(this, this.isFocusable, this.importantForAccessibility) {
|
|
- override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
|
|
- if (action == AccessibilityNodeInfo.ACTION_CLICK) {
|
|
- val length = checkNotNull(text).length
|
|
- if (length > 0) {
|
|
- // For some reason, when you swipe to focus on a text input that already has text in
|
|
- // it, it clears the selection and resets the cursor to the beginning of the input.
|
|
- // Since this is not typically (ever?) what you want, let's just explicitly set the
|
|
- // selection on accessibility click to undo that.
|
|
- setSelection(length)
|
|
+ object :
|
|
+ ReactAccessibilityDelegate(
|
|
+ this,
|
|
+ this.isFocusable,
|
|
+ this.importantForAccessibility
|
|
+ ) {
|
|
+ override fun performAccessibilityAction(
|
|
+ host: View,
|
|
+ action: Int,
|
|
+ args: Bundle?
|
|
+ ): Boolean {
|
|
+ if (action == AccessibilityNodeInfo.ACTION_CLICK) {
|
|
+ val length = checkNotNull(text).length
|
|
+ if (length > 0) {
|
|
+ // For some reason, when you swipe to focus on a text input that already has
|
|
+ // text in
|
|
+ // it, it clears the selection and resets the cursor to the beginning of the
|
|
+ // input.
|
|
+ // Since this is not typically (ever?) what you want, let's just explicitly set
|
|
+ // the
|
|
+ // selection on accessibility click to undo that.
|
|
+ setSelection(length)
|
|
+ }
|
|
+ return requestFocusProgrammatically()
|
|
+ }
|
|
+ return super.performAccessibilityAction(host, action, args)
|
|
}
|
|
- return requestFocusProgrammatically()
|
|
}
|
|
- return super.performAccessibilityAction(host, action, args)
|
|
- }
|
|
- }
|
|
ViewCompat.setAccessibilityDelegate(this, editTextAccessibilityDelegate)
|
|
val customActionModeCallback: ActionMode.Callback =
|
|
- object : ActionMode.Callback {
|
|
- /*
|
|
- * Editor onCreateActionMode adds the cut, copy, paste, share, autofill,
|
|
- * and paste as plain text items to the context menu.
|
|
- */
|
|
- override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
|
- if (contextMenuHidden) {
|
|
- return false
|
|
- }
|
|
- menu.removeItem(android.R.id.pasteAsPlainText)
|
|
- return true
|
|
- }
|
|
+ object : ActionMode.Callback {
|
|
+ /*
|
|
+ * Editor onCreateActionMode adds the cut, copy, paste, share, autofill,
|
|
+ * and paste as plain text items to the context menu.
|
|
+ */
|
|
+ override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
|
+ if (contextMenuHidden) {
|
|
+ return false
|
|
+ }
|
|
+ menu.removeItem(android.R.id.pasteAsPlainText)
|
|
+ return true
|
|
+ }
|
|
|
|
- override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = true
|
|
+ override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = true
|
|
|
|
- override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean = false
|
|
+ override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean = false
|
|
|
|
- override fun onDestroyActionMode(mode: ActionMode) = Unit
|
|
- }
|
|
+ override fun onDestroyActionMode(mode: ActionMode) = Unit
|
|
+ }
|
|
customSelectionActionModeCallback = customActionModeCallback
|
|
customInsertionActionModeCallback = customActionModeCallback
|
|
}
|
|
@@ -300,18 +320,18 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
// capturing these touches or not.
|
|
this.parent.requestDisallowInterceptTouchEvent(true)
|
|
}
|
|
-
|
|
MotionEvent.ACTION_MOVE ->
|
|
- if (detectScrollMovement) {
|
|
- if (!canScrollVertically(-1) &&
|
|
- !canScrollVertically(1) &&
|
|
- !canScrollHorizontally(-1) &&
|
|
- !canScrollHorizontally(1)) {
|
|
- // We cannot scroll, let parent views take care of these touches.
|
|
- this.parent.requestDisallowInterceptTouchEvent(false)
|
|
- }
|
|
- detectScrollMovement = false
|
|
- }
|
|
+ if (detectScrollMovement) {
|
|
+ if (!canScrollVertically(-1) &&
|
|
+ !canScrollVertically(1) &&
|
|
+ !canScrollHorizontally(-1) &&
|
|
+ !canScrollHorizontally(1)
|
|
+ ) {
|
|
+ // We cannot scroll, let parent views take care of these touches.
|
|
+ this.parent.requestDisallowInterceptTouchEvent(false)
|
|
+ }
|
|
+ detectScrollMovement = false
|
|
+ }
|
|
}
|
|
return super.onTouchEvent(ev)
|
|
}
|
|
@@ -341,8 +361,12 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
var inputConnection = super.onCreateInputConnection(outAttrs)
|
|
if (inputConnection != null && onKeyPress) {
|
|
inputConnection =
|
|
- ReactEditTextInputConnectionWrapper(
|
|
- inputConnection, reactContext, this, checkNotNull(eventDispatcher))
|
|
+ ReactEditTextInputConnectionWrapper(
|
|
+ inputConnection,
|
|
+ reactContext,
|
|
+ this,
|
|
+ checkNotNull(eventDispatcher)
|
|
+ )
|
|
}
|
|
|
|
if (isMultiline && (shouldBlurOnReturn() || shouldSubmitOnReturn())) {
|
|
@@ -356,9 +380,40 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
* Called when a context menu option for the text view is selected.
|
|
* React Native replaces copy (as rich text) with copy as plain text.
|
|
*/
|
|
- override fun onTextContextMenuItem(id: Int): Boolean =
|
|
- super.onTextContextMenuItem(
|
|
- if (id == android.R.id.paste) android.R.id.pasteAsPlainText else id)
|
|
+ override fun onTextContextMenuItem(id: Int): Boolean {
|
|
+ var newId = id
|
|
+ if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) {
|
|
+ newId = android.R.id.pasteAsPlainText
|
|
+ pasteWatcher?.let { watcher ->
|
|
+ val clipboardManager =
|
|
+ context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
+ val clipData = clipboardManager.primaryClip
|
|
+
|
|
+ var type: String? = null
|
|
+ var data: String? = null
|
|
+
|
|
+ if (clipData != null && clipData.itemCount > 0) {
|
|
+ val desc = clipData.description
|
|
+ if (desc.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
|
+ type = ClipDescription.MIMETYPE_TEXT_PLAIN
|
|
+ data = clipData.getItemAt(0).text?.toString()
|
|
+ } else {
|
|
+ val itemUri = clipData.getItemAt(0).uri
|
|
+ if (itemUri != null) {
|
|
+ val cr = getReactContext(this).contentResolver
|
|
+ type = cr.getType(itemUri)
|
|
+ data = itemUri.toString()
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (type != null && data != null) {
|
|
+ watcher.onPaste(type, data)
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return super.onTextContextMenuItem(newId)
|
|
+ }
|
|
|
|
internal fun clearFocusAndMaybeRefocus() {
|
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P || !isInTouchMode) {
|
|
@@ -421,6 +476,10 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
this.scrollWatcher = scrollWatcher
|
|
}
|
|
|
|
+ public fun setPasteWatcher(pasteWatcher: PasteWatcher?) {
|
|
+ this.pasteWatcher = pasteWatcher
|
|
+ }
|
|
+
|
|
/**
|
|
* Attempt to set a selection or fail silently. Intentionally meant to handle bad inputs.
|
|
* EventCounter is the same one used as with text.
|
|
@@ -492,11 +551,11 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
// Default shouldBlur
|
|
val shouldBlur =
|
|
- if (submitBehavior == null) {
|
|
- !isMultiline
|
|
- } else {
|
|
- submitBehavior == "blurAndSubmit"
|
|
- }
|
|
+ if (submitBehavior == null) {
|
|
+ !isMultiline
|
|
+ } else {
|
|
+ submitBehavior == "blurAndSubmit"
|
|
+ }
|
|
|
|
return shouldBlur
|
|
}
|
|
@@ -506,11 +565,11 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
// Default shouldSubmit
|
|
val shouldSubmit =
|
|
- if (submitBehavior == null) {
|
|
- !isMultiline
|
|
- } else {
|
|
- submitBehavior == "submit" || submitBehavior == "blurAndSubmit"
|
|
- }
|
|
+ if (submitBehavior == null) {
|
|
+ !isMultiline
|
|
+ } else {
|
|
+ submitBehavior == "submit" || submitBehavior == "blurAndSubmit"
|
|
+ }
|
|
|
|
return shouldSubmit
|
|
}
|
|
@@ -600,14 +659,15 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
// Match behavior of CustomStyleSpan and enable SUBPIXEL_TEXT_FLAG when setting anything
|
|
// nonstandard
|
|
paintFlags =
|
|
- if (fontStyle != ReactConstants.UNSET ||
|
|
- fontWeight != ReactConstants.UNSET ||
|
|
- fontFamily != null ||
|
|
- fontFeatureSettings != null) {
|
|
- paintFlags or Paint.SUBPIXEL_TEXT_FLAG
|
|
- } else {
|
|
- paintFlags and (Paint.SUBPIXEL_TEXT_FLAG.inv())
|
|
- }
|
|
+ if (fontStyle != ReactConstants.UNSET ||
|
|
+ fontWeight != ReactConstants.UNSET ||
|
|
+ fontFamily != null ||
|
|
+ fontFeatureSettings != null
|
|
+ ) {
|
|
+ paintFlags or Paint.SUBPIXEL_TEXT_FLAG
|
|
+ } else {
|
|
+ paintFlags and (Paint.SUBPIXEL_TEXT_FLAG.inv())
|
|
+ }
|
|
}
|
|
|
|
public fun requestFocusFromJS() {
|
|
@@ -642,8 +702,14 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
if (DEBUG_MODE) {
|
|
FLog.e(
|
|
- TAG,
|
|
- ("maybeSetText[" + id + "]: current text: " + text + " update: " + reactTextUpdate.text))
|
|
+ TAG,
|
|
+ ("maybeSetText[" +
|
|
+ id +
|
|
+ "]: current text: " +
|
|
+ text +
|
|
+ " update: " +
|
|
+ reactTextUpdate.text)
|
|
+ )
|
|
}
|
|
|
|
// The current text gets replaced with the text received from JS. However, the spans on the
|
|
@@ -655,8 +721,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
manageSpans(spannableStringBuilder)
|
|
stripStyleEquivalentSpans(spannableStringBuilder)
|
|
|
|
- @Suppress("DEPRECATION")
|
|
- containsImages = reactTextUpdate.containsImages()
|
|
+ @Suppress("DEPRECATION") containsImages = reactTextUpdate.containsImages()
|
|
|
|
// When we update text, we trigger onChangeText code that will
|
|
// try to update state if the wrapper is available. Temporarily disable
|
|
@@ -695,7 +760,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
val span = spans[spanIdx]
|
|
val spanFlags = text.getSpanFlags(span)
|
|
val isExclusiveExclusive =
|
|
- (spanFlags and Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
+ (spanFlags and Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
// Remove all styling spans we might have previously set
|
|
if (span is ReactSpan) {
|
|
@@ -751,16 +816,16 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
stripSpansOfKind(sb, CustomStyleSpan::class.java) { span: CustomStyleSpan ->
|
|
span.style == fontStyle &&
|
|
- span.fontFamily == fontFamily &&
|
|
- span.weight == fontWeight &&
|
|
- span.fontFeatureSettings == fontFeatureSettings
|
|
+ span.fontFamily == fontFamily &&
|
|
+ span.weight == fontWeight &&
|
|
+ span.fontFeatureSettings == fontFeatureSettings
|
|
}
|
|
}
|
|
|
|
private fun <T> stripSpansOfKind(
|
|
- sb: SpannableStringBuilder,
|
|
- clazz: Class<T>,
|
|
- shouldStrip: Predicate<T>
|
|
+ sb: SpannableStringBuilder,
|
|
+ clazz: Class<T>,
|
|
+ shouldStrip: Predicate<T>
|
|
) {
|
|
val spans = sb.getSpans(0, sb.length, clazz)
|
|
|
|
@@ -783,15 +848,27 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
spanFlags = spanFlags or Spannable.SPAN_PRIORITY
|
|
|
|
workingText.setSpan(
|
|
- ReactAbsoluteSizeSpan(textAttributes.effectiveFontSize), 0, workingText.length, spanFlags)
|
|
+ ReactAbsoluteSizeSpan(textAttributes.effectiveFontSize),
|
|
+ 0,
|
|
+ workingText.length,
|
|
+ spanFlags
|
|
+ )
|
|
|
|
workingText.setSpan(
|
|
- ReactForegroundColorSpan(currentTextColor), 0, workingText.length, spanFlags)
|
|
+ ReactForegroundColorSpan(currentTextColor),
|
|
+ 0,
|
|
+ workingText.length,
|
|
+ spanFlags
|
|
+ )
|
|
|
|
val backgroundColor = getBackgroundColor(this)
|
|
if (backgroundColor != null && backgroundColor != Color.TRANSPARENT) {
|
|
workingText.setSpan(
|
|
- ReactBackgroundColorSpan(backgroundColor), 0, workingText.length, spanFlags)
|
|
+ ReactBackgroundColorSpan(backgroundColor),
|
|
+ 0,
|
|
+ workingText.length,
|
|
+ spanFlags
|
|
+ )
|
|
}
|
|
|
|
if ((paintFlags and Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
|
|
@@ -805,18 +882,30 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
val effectiveLetterSpacing = textAttributes.effectiveLetterSpacing
|
|
if (!effectiveLetterSpacing.isNaN()) {
|
|
workingText.setSpan(
|
|
- CustomLetterSpacingSpan(effectiveLetterSpacing), 0, workingText.length, spanFlags)
|
|
+ CustomLetterSpacingSpan(effectiveLetterSpacing),
|
|
+ 0,
|
|
+ workingText.length,
|
|
+ spanFlags
|
|
+ )
|
|
}
|
|
|
|
if (fontStyle != ReactConstants.UNSET ||
|
|
- fontWeight != ReactConstants.UNSET ||
|
|
- fontFamily != null ||
|
|
- fontFeatureSettings != null) {
|
|
+ fontWeight != ReactConstants.UNSET ||
|
|
+ fontFamily != null ||
|
|
+ fontFeatureSettings != null
|
|
+ ) {
|
|
workingText.setSpan(
|
|
- CustomStyleSpan(fontStyle, fontWeight, fontFeatureSettings, fontFamily, context.assets),
|
|
- 0,
|
|
- workingText.length,
|
|
- spanFlags)
|
|
+ CustomStyleSpan(
|
|
+ fontStyle,
|
|
+ fontWeight,
|
|
+ fontFeatureSettings,
|
|
+ fontFamily,
|
|
+ context.assets
|
|
+ ),
|
|
+ 0,
|
|
+ workingText.length,
|
|
+ spanFlags
|
|
+ )
|
|
}
|
|
|
|
val lineHeight = textAttributes.effectiveLineHeight
|
|
@@ -870,11 +959,11 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
}
|
|
|
|
imeOptions =
|
|
- if (disableFullscreen) {
|
|
- returnKeyFlag or EditorInfo.IME_FLAG_NO_FULLSCREEN
|
|
- } else {
|
|
- returnKeyFlag
|
|
- }
|
|
+ if (disableFullscreen) {
|
|
+ returnKeyFlag or EditorInfo.IME_FLAG_NO_FULLSCREEN
|
|
+ } else {
|
|
+ returnKeyFlag
|
|
+ }
|
|
}
|
|
|
|
override fun verifyDrawable(drawable: Drawable): Boolean {
|
|
@@ -929,7 +1018,8 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
super.onConfigurationChanged(newConfig)
|
|
|
|
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
|
- ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
|
|
+ ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()
|
|
+ ) {
|
|
applyTextAttributes()
|
|
}
|
|
}
|
|
@@ -987,7 +1077,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
}
|
|
|
|
public fun getBorderColor(position: Int): Int =
|
|
- getBorderColor(this, LogicalEdge.entries[position]) ?: Color.TRANSPARENT
|
|
+ getBorderColor(this, LogicalEdge.entries[position]) ?: Color.TRANSPARENT
|
|
|
|
public fun setBorderRadius(borderRadius: Float) {
|
|
setBorderRadius(borderRadius, BorderRadiusProp.BORDER_RADIUS.ordinal)
|
|
@@ -995,11 +1085,11 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
public fun setBorderRadius(borderRadius: Float, position: Int) {
|
|
val radius =
|
|
- if (borderRadius.isNaN()) {
|
|
- null
|
|
- } else {
|
|
- LengthPercentage(toDIPFromPixel(borderRadius), LengthPercentageType.POINT)
|
|
- }
|
|
+ if (borderRadius.isNaN()) {
|
|
+ null
|
|
+ } else {
|
|
+ LengthPercentage(toDIPFromPixel(borderRadius), LengthPercentageType.POINT)
|
|
+ }
|
|
setBorderRadius(this, BorderRadiusProp.entries[position], radius)
|
|
}
|
|
|
|
@@ -1134,10 +1224,11 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
|
|
addSpansFromStyleAttributes(sb)
|
|
sb.setSpan(
|
|
- ReactTextPaintHolderSpan(TextPaint(paint)),
|
|
- 0,
|
|
- sb.length,
|
|
- Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
|
+ ReactTextPaintHolderSpan(TextPaint(paint)),
|
|
+ 0,
|
|
+ sb.length,
|
|
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE
|
|
+ )
|
|
TextLayoutManager.setCachedSpannableForTag(id, sb)
|
|
}
|
|
|
|
@@ -1227,6 +1318,48 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
|
|
_inputType = inputType
|
|
}
|
|
|
|
+ /*
|
|
+ * All overrides of key handling defer to the underlying KeyListener which is shared by all
|
|
+ * ReactEditText instances. It will basically allow any/all keyboard input whether from
|
|
+ * physical keyboard or from soft input.
|
|
+ */
|
|
+ override fun onKeyDown(view: View, text: Editable, keyCode: Int, event: KeyEvent): Boolean =
|
|
+ keyListener.onKeyDown(view, text, keyCode, event)
|
|
+
|
|
+ override fun onKeyUp(view: View, text: Editable, keyCode: Int, event: KeyEvent): Boolean =
|
|
+ keyListener.onKeyUp(view, text, keyCode, event)
|
|
+
|
|
+ override fun onKeyOther(view: View, text: Editable, event: KeyEvent): Boolean =
|
|
+ keyListener.onKeyOther(view, text, event)
|
|
+
|
|
+ override fun clearMetaKeyState(view: View, content: Editable, states: Int) {
|
|
+ keyListener.clearMetaKeyState(view, content, states)
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public companion object {
|
|
+ public val DEBUG_MODE: Boolean = ReactBuildConfig.DEBUG && false
|
|
+
|
|
+ private val keyListener: KeyListener = QwertyKeyListener.getInstanceForFullKeyboard()
|
|
+
|
|
+ private fun sameTextForSpan(
|
|
+ oldText: Editable,
|
|
+ newText: SpannableStringBuilder,
|
|
+ start: Int,
|
|
+ end: Int
|
|
+ ): Boolean {
|
|
+ if (start > newText.length || end > newText.length) {
|
|
+ return false
|
|
+ }
|
|
+ for (charIdx in start ..< end) {
|
|
+ if (oldText[charIdx] != newText[charIdx]) {
|
|
+ return false
|
|
+ }
|
|
+ }
|
|
+ return true
|
|
+ }
|
|
+ }
|
|
+}
|
|
/*
|
|
* All overrides of key handling defer to the underlying KeyListener which is shared by all
|
|
* ReactEditText instances. It will basically allow any/all keyboard input whether from
|
|
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt
|
|
index 42c13a1..597770b 100644
|
|
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt
|
|
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt
|
|
@@ -132,7 +132,11 @@ public open class ReactTextInputManager public constructor() :
|
|
val baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants()
|
|
val eventTypeConstants = baseEventTypeConstants ?: mutableMapOf()
|
|
eventTypeConstants.putAll(
|
|
- mapOf(getJSEventName(ScrollEventType.SCROLL) to mapOf("registrationName" to "onScroll")))
|
|
+ mapOf(
|
|
+ getJSEventName(ScrollEventType.SCROLL) to mapOf("registrationName" to "onScroll"),
|
|
+ "topPaste" to mapOf("registrationName" to "onPaste"),
|
|
+ )
|
|
+ )
|
|
return eventTypeConstants
|
|
}
|
|
|
|
@@ -327,6 +331,15 @@ public open class ReactTextInputManager public constructor() :
|
|
}
|
|
}
|
|
|
|
+ @ReactProp(name = "onPaste", defaultBoolean = false)
|
|
+ fun setOnPaste(view: ReactEditText, onPaste: Boolean) {
|
|
+ if (onPaste) {
|
|
+ view.setPasteWatcher(ReactTextPasteWatcher(view))
|
|
+ } else {
|
|
+ view.setPasteWatcher(null)
|
|
+ }
|
|
+ }
|
|
+
|
|
@ReactProp(name = "onKeyPress", defaultBoolean = false)
|
|
public fun setOnKeyPress(view: ReactEditText, onKeyPress: Boolean) {
|
|
view.setOnKeyPress(onKeyPress)
|
|
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt
|
|
new file mode 100644
|
|
index 0000000..91396dc
|
|
--- /dev/null
|
|
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt
|
|
@@ -0,0 +1,51 @@
|
|
+/*
|
|
+ * 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.bridge.Arguments
|
|
+import com.facebook.react.bridge.WritableArray
|
|
+import com.facebook.react.bridge.WritableMap
|
|
+import com.facebook.react.uimanager.common.ViewUtil
|
|
+import com.facebook.react.uimanager.events.Event
|
|
+
|
|
+/**
|
|
+ * Event emitted by ReactEditText when clipboard content is pasted.
|
|
+ */
|
|
+internal class ReactTextInputPasteEvent(
|
|
+ surfaceId: Int,
|
|
+ viewId: Int,
|
|
+ private val type: String,
|
|
+ private val data: String
|
|
+) : Event<ReactTextInputPasteEvent>(surfaceId, viewId) {
|
|
+
|
|
+ @Deprecated("Use the constructor with surfaceId.")
|
|
+ constructor(viewId: Int, type: String, data: String) :
|
|
+ this(ViewUtil.NO_SURFACE_ID, viewId, type, data)
|
|
+
|
|
+ override fun getEventName(): String = EVENT_NAME
|
|
+
|
|
+ override fun canCoalesce(): Boolean = false
|
|
+
|
|
+ override fun getEventData(): WritableMap? {
|
|
+ val eventData: WritableMap = Arguments.createMap()
|
|
+
|
|
+ val items: WritableArray = Arguments.createArray()
|
|
+ val item: WritableMap = Arguments.createMap()
|
|
+ item.putString("type", type)
|
|
+ item.putString("data", data)
|
|
+ items.pushMap(item)
|
|
+
|
|
+ eventData.putArray("items", items)
|
|
+
|
|
+ return eventData
|
|
+ }
|
|
+
|
|
+ private companion object {
|
|
+ private const val EVENT_NAME = "topPaste"
|
|
+ }
|
|
+}
|
|
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextPasteWatcher.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextPasteWatcher.kt
|
|
new file mode 100644
|
|
index 0000000..5eb9647
|
|
--- /dev/null
|
|
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextPasteWatcher.kt
|
|
@@ -0,0 +1,31 @@
|
|
+/*
|
|
+ * 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.UIManagerHelper
|
|
+import com.facebook.react.uimanager.events.EventDispatcher
|
|
+
|
|
+/**
|
|
+ * Dispatches a paste event from a ReactEditText to JS.
|
|
+ */
|
|
+internal class ReactTextPasteWatcher(private val editText: ReactEditText) : PasteWatcher {
|
|
+ private val eventDispatcher: EventDispatcher?
|
|
+ private val surfaceId: Int
|
|
+
|
|
+ init {
|
|
+ val reactContext = UIManagerHelper.getReactContext(editText)
|
|
+ eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, editText.id)
|
|
+ surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
+ }
|
|
+
|
|
+ override fun onPaste(type: String, data: String) {
|
|
+ eventDispatcher?.dispatchEvent(
|
|
+ ReactTextInputPasteEvent(surfaceId, editText.id, type, data)
|
|
+ )
|
|
+ }
|
|
+}
|
|
diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp
|
|
index a9bc219..7ebec12 100644
|
|
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp
|
|
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp
|
|
@@ -177,6 +177,19 @@ void TextInputEventEmitter::onScroll(const Metrics& textInputMetrics) const {
|
|
});
|
|
}
|
|
|
|
+void TextInputEventEmitter::onPaste(const std::string& type, const std::string& data) const {
|
|
+ dispatchEvent("onPaste", [type, data](jsi::Runtime& runtime) {
|
|
+ auto payload = jsi::Object(runtime);
|
|
+ auto items = jsi::Array(runtime, 1);
|
|
+ auto item = jsi::Object(runtime);
|
|
+ item.setProperty(runtime, "type", type);
|
|
+ item.setProperty(runtime, "data", data);
|
|
+ items.setValueAtIndex(runtime, 0, item);
|
|
+ payload.setProperty(runtime, "items", items);
|
|
+ return payload;
|
|
+ });
|
|
+}
|
|
+
|
|
void TextInputEventEmitter::dispatchTextInputEvent(
|
|
const std::string& name,
|
|
const Metrics& textInputMetrics,
|
|
diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h
|
|
index dbce575..5b03581 100644
|
|
--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h
|
|
+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h
|
|
@@ -44,6 +44,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 std::string& type, const std::string& data) const;
|
|
|
|
private:
|
|
void dispatchTextInputEvent(
|