react-native-typerich 1.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -10
- package/ReactNativeTypeRich.podspec +41 -0
- package/android/src/main/java/com/typerich/TypeRichTextInputView.kt +37 -10
- package/android/src/main/java/com/typerich/TypeRichTextInputViewManager.kt +5 -0
- package/ios/TypeRichTextInputView.h +27 -7
- package/ios/TypeRichTextInputView.mm +809 -26
- package/ios/cpp/TypeRichTextInputViewComponentDescriptor.h +19 -0
- package/ios/cpp/TypeRichTextInputViewShadowNode.h +44 -0
- package/ios/cpp/TypeRichTextInputViewShadowNode.mm +110 -0
- package/ios/cpp/TypeRichTextInputViewState.cpp +10 -0
- package/ios/cpp/TypeRichTextInputViewState.h +22 -0
- package/ios/inputTextView/TypeRichUITextView.h +14 -0
- package/ios/inputTextView/TypeRichUITextView.mm +100 -0
- package/ios/modules/commands/TypeRichTextInputCommands.h +24 -0
- package/ios/modules/commands/TypeRichTextInputCommands.mm +392 -0
- package/ios/utils/StringUtils.h +19 -0
- package/ios/utils/StringUtils.mm +15 -0
- package/ios/utils/TextInputUtils.h +26 -0
- package/ios/utils/TextInputUtils.mm +58 -0
- package/lib/module/TypeRichTextInput.js +13 -36
- package/lib/module/TypeRichTextInput.js.map +1 -1
- package/lib/module/TypeRichTextInputNativeComponent.ts +266 -52
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types/TypeRichTextInput.js +4 -0
- package/lib/module/types/TypeRichTextInput.js.map +1 -0
- package/lib/typescript/src/TypeRichTextInput.d.ts +2 -22
- package/lib/typescript/src/TypeRichTextInput.d.ts.map +1 -1
- package/lib/typescript/src/TypeRichTextInputNativeComponent.d.ts +200 -14
- package/lib/typescript/src/TypeRichTextInputNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/TypeRichTextInput.d.ts +95 -0
- package/lib/typescript/src/types/TypeRichTextInput.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/TypeRichTextInput.tsx +20 -70
- package/src/TypeRichTextInputNativeComponent.ts +266 -52
- package/src/index.tsx +1 -5
- package/src/types/TypeRichTextInput.tsx +116 -0
- package/TypeRichTextInput.podspec +0 -20
- package/ios/TypeRichTextInputViewManager.mm +0 -27
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "TypeRichTextInputViewShadowNode.h"
|
|
3
|
+
#include <react/debug/react_native_assert.h>
|
|
4
|
+
#include <react/renderer/components/TypeRichTextInputViewSpec/Props.h>
|
|
5
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
6
|
+
|
|
7
|
+
namespace facebook::react {
|
|
8
|
+
class TypeRichTextInputViewComponentDescriptor final
|
|
9
|
+
: public ConcreteComponentDescriptor<TypeRichTextInputViewShadowNode> {
|
|
10
|
+
public:
|
|
11
|
+
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
|
|
12
|
+
void adopt(ShadowNode &shadowNode) const override {
|
|
13
|
+
react_native_assert(
|
|
14
|
+
dynamic_cast<TypeRichTextInputViewShadowNode *>(&shadowNode));
|
|
15
|
+
ConcreteComponentDescriptor::adopt(shadowNode);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "TypeRichTextInputViewState.h"
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
#include <react/renderer/components/TypeRichTextInputViewSpec/EventEmitters.h>
|
|
5
|
+
#include <react/renderer/components/TypeRichTextInputViewSpec/Props.h>
|
|
6
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
7
|
+
#include <react/renderer/core/LayoutConstraints.h>
|
|
8
|
+
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
|
|
11
|
+
JSI_EXPORT extern const char TypeRichTextInputViewComponentName[];
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* `ShadowNode` for <TypeRichTextInputView> component.
|
|
15
|
+
*/
|
|
16
|
+
class TypeRichTextInputViewShadowNode
|
|
17
|
+
: public ConcreteViewShadowNode<
|
|
18
|
+
TypeRichTextInputViewComponentName, TypeRichTextInputViewProps,
|
|
19
|
+
TypeRichTextInputViewEventEmitter, TypeRichTextInputViewState> {
|
|
20
|
+
public:
|
|
21
|
+
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
|
22
|
+
TypeRichTextInputViewShadowNode(const ShadowNodeFragment &fragment,
|
|
23
|
+
const ShadowNodeFamily::Shared &family,
|
|
24
|
+
ShadowNodeTraits traits);
|
|
25
|
+
TypeRichTextInputViewShadowNode(const ShadowNode &sourceShadowNode,
|
|
26
|
+
const ShadowNodeFragment &fragment);
|
|
27
|
+
void dirtyLayoutIfNeeded();
|
|
28
|
+
Size
|
|
29
|
+
measureContent(const LayoutContext &layoutContext,
|
|
30
|
+
const LayoutConstraints &layoutConstraints) const override;
|
|
31
|
+
|
|
32
|
+
static ShadowNodeTraits BaseTraits() {
|
|
33
|
+
auto traits = ConcreteViewShadowNode::BaseTraits();
|
|
34
|
+
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
|
|
35
|
+
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
|
|
36
|
+
return traits;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private:
|
|
40
|
+
int localForceHeightRecalculationCounter_;
|
|
41
|
+
id setupMockTextInputView_() const;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#import "TypeRichTextInputViewShadowNode.h"
|
|
2
|
+
#import "CoreText/CoreText.h"
|
|
3
|
+
#import <TypeRichTextInputView.h>
|
|
4
|
+
#import <React/RCTShadowView+Layout.h>
|
|
5
|
+
#import <react/utils/ManagedObjectWrapper.h>
|
|
6
|
+
#import <yoga/Yoga.h>
|
|
7
|
+
#import <React/RCTLog.h>
|
|
8
|
+
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
|
|
11
|
+
extern const char TypeRichTextInputViewComponentName[] =
|
|
12
|
+
"TypeRichTextInputView";
|
|
13
|
+
|
|
14
|
+
TypeRichTextInputViewShadowNode::TypeRichTextInputViewShadowNode(
|
|
15
|
+
const ShadowNodeFragment &fragment, const ShadowNodeFamily::Shared &family,
|
|
16
|
+
ShadowNodeTraits traits)
|
|
17
|
+
: ConcreteViewShadowNode(fragment, family, traits) {
|
|
18
|
+
localForceHeightRecalculationCounter_ = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// mock input is used for the first measure calls that need to be done when the
|
|
22
|
+
// real input isn't defined yet
|
|
23
|
+
id TypeRichTextInputViewShadowNode::setupMockTextInputView_() const {
|
|
24
|
+
// it's rendered far away from the viewport
|
|
25
|
+
const int veryFarAway = 20000;
|
|
26
|
+
const int mockSize = 1000;
|
|
27
|
+
TypeRichTextInputView *mockTextInputView_ = [[TypeRichTextInputView alloc]
|
|
28
|
+
initWithFrame:(CGRectMake(veryFarAway, veryFarAway, mockSize, mockSize))];
|
|
29
|
+
const auto props = this->getProps();
|
|
30
|
+
mockTextInputView_.blockEmitting = YES;
|
|
31
|
+
[mockTextInputView_ updateProps:props oldProps:nullptr];
|
|
32
|
+
return mockTextInputView_;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
TypeRichTextInputViewShadowNode::TypeRichTextInputViewShadowNode(
|
|
36
|
+
const ShadowNode &sourceShadowNode, const ShadowNodeFragment &fragment)
|
|
37
|
+
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
|
|
38
|
+
dirtyLayoutIfNeeded();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void TypeRichTextInputViewShadowNode::dirtyLayoutIfNeeded() {
|
|
42
|
+
const auto state = this->getStateData();
|
|
43
|
+
const int receivedCounter = state.getForceHeightRecalculationCounter();
|
|
44
|
+
|
|
45
|
+
if (receivedCounter > localForceHeightRecalculationCounter_) {
|
|
46
|
+
localForceHeightRecalculationCounter_ = receivedCounter;
|
|
47
|
+
YGNodeMarkDirty(&yogaNode_);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Size TypeRichTextInputViewShadowNode::measureContent(
|
|
52
|
+
const LayoutContext &layoutContext,
|
|
53
|
+
const LayoutConstraints &layoutConstraints) const {
|
|
54
|
+
const auto state = this->getStateData();
|
|
55
|
+
const auto componentRef = state.getComponentViewRef();
|
|
56
|
+
RCTInternalGenericWeakWrapper *weakWrapper =
|
|
57
|
+
(RCTInternalGenericWeakWrapper *)unwrapManagedObject(componentRef);
|
|
58
|
+
|
|
59
|
+
RCTLogInfo(
|
|
60
|
+
@"[TypeRichTextInput] minH=%f maxH=%f",
|
|
61
|
+
layoutConstraints.minimumSize.height,
|
|
62
|
+
layoutConstraints.maximumSize.height
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (weakWrapper != nullptr) {
|
|
66
|
+
id componentObject = weakWrapper.object;
|
|
67
|
+
TypeRichTextInputView *typedComponentObject =
|
|
68
|
+
(TypeRichTextInputView *)componentObject;
|
|
69
|
+
|
|
70
|
+
if (typedComponentObject != nullptr) {
|
|
71
|
+
__block CGSize estimatedSize;
|
|
72
|
+
|
|
73
|
+
// synchronously dispatch to main thread if needed
|
|
74
|
+
if ([NSThread isMainThread]) {
|
|
75
|
+
estimatedSize = [typedComponentObject
|
|
76
|
+
measureSize:layoutConstraints.maximumSize.width];
|
|
77
|
+
} else {
|
|
78
|
+
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
79
|
+
estimatedSize = [typedComponentObject
|
|
80
|
+
measureSize:layoutConstraints.maximumSize.width];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {estimatedSize.width,
|
|
85
|
+
MIN(estimatedSize.height, layoutConstraints.maximumSize.height)};
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
__block CGSize estimatedSize;
|
|
89
|
+
|
|
90
|
+
// synchronously dispatch to main thread if needed
|
|
91
|
+
if ([NSThread isMainThread]) {
|
|
92
|
+
TypeRichTextInputView *mockTextInputView = setupMockTextInputView_();
|
|
93
|
+
estimatedSize =
|
|
94
|
+
[mockTextInputView measureSize:layoutConstraints.maximumSize.width];
|
|
95
|
+
} else {
|
|
96
|
+
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
97
|
+
TypeRichTextInputView *mockTextInputView = setupMockTextInputView_();
|
|
98
|
+
estimatedSize =
|
|
99
|
+
[mockTextInputView measureSize:layoutConstraints.maximumSize.width];
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {estimatedSize.width,
|
|
104
|
+
MIN(estimatedSize.height, layoutConstraints.maximumSize.height)};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Size();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#include "TypeRichTextInputViewState.h"
|
|
2
|
+
|
|
3
|
+
namespace facebook::react {
|
|
4
|
+
int TypeRichTextInputViewState::getForceHeightRecalculationCounter() const {
|
|
5
|
+
return forceHeightRecalculationCounter_;
|
|
6
|
+
}
|
|
7
|
+
std::shared_ptr<void> TypeRichTextInputViewState::getComponentViewRef() const {
|
|
8
|
+
return componentViewRef_;
|
|
9
|
+
}
|
|
10
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <memory>
|
|
3
|
+
|
|
4
|
+
namespace facebook::react {
|
|
5
|
+
|
|
6
|
+
class TypeRichTextInputViewState {
|
|
7
|
+
public:
|
|
8
|
+
TypeRichTextInputViewState()
|
|
9
|
+
: forceHeightRecalculationCounter_(0), componentViewRef_(nullptr) {}
|
|
10
|
+
TypeRichTextInputViewState(int counter, std::shared_ptr<void> ref) {
|
|
11
|
+
forceHeightRecalculationCounter_ = counter;
|
|
12
|
+
componentViewRef_ = ref;
|
|
13
|
+
}
|
|
14
|
+
int getForceHeightRecalculationCounter() const;
|
|
15
|
+
std::shared_ptr<void> getComponentViewRef() const;
|
|
16
|
+
|
|
17
|
+
private:
|
|
18
|
+
int forceHeightRecalculationCounter_{};
|
|
19
|
+
std::shared_ptr<void> componentViewRef_{};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// InputTextView.h
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Div on 29/12/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <UIKit/UIKit.h>
|
|
9
|
+
|
|
10
|
+
@class TypeRichTextInputView;
|
|
11
|
+
|
|
12
|
+
@interface TypeRichUITextView : UITextView
|
|
13
|
+
@property (nonatomic, weak) TypeRichTextInputView *owner;
|
|
14
|
+
@end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
//
|
|
2
|
+
// InputTextView.m
|
|
3
|
+
// ReactNativeTypeRich
|
|
4
|
+
//
|
|
5
|
+
// Created by Div on 29/12/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import "TypeRichUITextView.h"
|
|
9
|
+
#import "TypeRichTextInputView.h"
|
|
10
|
+
|
|
11
|
+
@implementation TypeRichUITextView
|
|
12
|
+
|
|
13
|
+
- (void)paste:(id)sender {
|
|
14
|
+
UIPasteboard *pb = UIPasteboard.generalPasteboard;
|
|
15
|
+
|
|
16
|
+
if ([self.owner isDisableImagePasting] &&
|
|
17
|
+
pb.hasImages &&
|
|
18
|
+
!pb.hasStrings) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
![self.owner isDisableImagePasting]
|
|
24
|
+
&& pb.hasImages
|
|
25
|
+
) {
|
|
26
|
+
|
|
27
|
+
UIImage *image = pb.image;
|
|
28
|
+
if (!image) {
|
|
29
|
+
[super paste:sender];
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// NSData *data = UIImagePNGRepresentation(image); // when the requirement is for transparency enable this
|
|
34
|
+
NSData *data = UIImageJPEGRepresentation(image, 0.8);
|
|
35
|
+
|
|
36
|
+
if (!data) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
NSString *fileName =
|
|
41
|
+
[NSString stringWithFormat:@"typerich_%@.png", NSUUID.UUID.UUIDString];
|
|
42
|
+
|
|
43
|
+
NSString *path =
|
|
44
|
+
[NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
|
|
45
|
+
|
|
46
|
+
NSError *error = nil;
|
|
47
|
+
if (![data writeToFile:path options:NSDataWritingAtomic error:&error]) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
[self.owner emitPasteImageEventWith:path
|
|
52
|
+
type:@"image/png"
|
|
53
|
+
fileName:fileName
|
|
54
|
+
fileSize:data.length];
|
|
55
|
+
|
|
56
|
+
// Prevent attachment insertion
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[super paste:sender];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
- (BOOL)canPasteItemProviders:(NSArray<NSItemProvider *> *)itemProviders {
|
|
65
|
+
for (NSItemProvider *provider in itemProviders) {
|
|
66
|
+
if ([provider hasItemConformingToTypeIdentifier:@"public.text"]) {
|
|
67
|
+
return YES;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (![self.owner isDisableImagePasting] &&
|
|
71
|
+
[provider hasItemConformingToTypeIdentifier:@"public.image"]) {
|
|
72
|
+
return YES;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return NO;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
|
|
79
|
+
if (action == @selector(paste:)) {
|
|
80
|
+
UIPasteboard *pb = UIPasteboard.generalPasteboard;
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
[self.owner isDisableImagePasting] &&
|
|
84
|
+
pb.hasImages &&
|
|
85
|
+
!pb.hasStrings) {
|
|
86
|
+
return NO;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Allow paste if there is text OR image
|
|
90
|
+
if (pb.hasStrings || pb.hasImages) {
|
|
91
|
+
return YES;
|
|
92
|
+
}
|
|
93
|
+
return NO;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return [super canPerformAction:action withSender:sender];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@end
|
|
100
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TypeRichTextInputCommands.h
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Div on 29/12/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <UIKit/UIKit.h>
|
|
9
|
+
#import "TypeRichTextInputView.h"
|
|
10
|
+
|
|
11
|
+
@interface TypeRichTextInputCommands : NSObject
|
|
12
|
+
@property (nonatomic, strong) NSOperationQueue *commandQueue;
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithTextView:(UITextView *)textView
|
|
15
|
+
owner:(TypeRichTextInputView *)owner;
|
|
16
|
+
|
|
17
|
+
- (void)focus;
|
|
18
|
+
- (void)blur;
|
|
19
|
+
- (void)setText:(NSString *)text;
|
|
20
|
+
- (void)setSelectionStart:(NSInteger)start end:(NSInteger)end;
|
|
21
|
+
- (void)insertTextAtStart:(NSInteger)start
|
|
22
|
+
end:(NSInteger)end
|
|
23
|
+
text:(NSString *)text;
|
|
24
|
+
@end
|