react-native-enriched 0.0.0 → 0.1.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.
Files changed (169) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +869 -0
  3. package/ReactNativeEnriched.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
  7. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  10. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  11. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  12. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
  13. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  15. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +3 -0
  19. package/android/src/main/AndroidManifestNew.xml +2 -0
  20. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
  21. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
  22. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
  23. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
  24. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
  25. package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
  26. package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
  27. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
  28. package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
  29. package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
  30. package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
  31. package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
  32. package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
  33. package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
  34. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
  35. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
  36. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
  37. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
  38. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
  39. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
  40. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
  41. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
  42. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
  43. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
  44. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
  45. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
  46. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
  47. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
  48. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
  49. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
  50. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
  51. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
  52. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
  53. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
  54. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
  55. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
  56. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
  57. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
  58. package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
  59. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
  60. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
  61. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
  62. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
  63. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
  64. package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
  65. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
  66. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
  67. package/android/src/main/new_arch/CMakeLists.txt +56 -0
  68. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
  69. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
  70. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
  71. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
  72. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
  73. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
  74. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
  75. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
  76. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
  77. package/ios/EnrichedTextInputView.h +33 -0
  78. package/ios/EnrichedTextInputView.mm +1190 -0
  79. package/ios/EnrichedTextInputViewManager.mm +13 -0
  80. package/ios/config/InputConfig.h +67 -0
  81. package/ios/config/InputConfig.mm +382 -0
  82. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  83. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  84. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  85. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  86. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  87. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
  88. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
  89. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  90. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  91. package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  92. package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
  93. package/ios/inputParser/InputParser.h +11 -0
  94. package/ios/inputParser/InputParser.mm +669 -0
  95. package/ios/inputTextView/InputTextView.h +6 -0
  96. package/ios/inputTextView/InputTextView.mm +115 -0
  97. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
  98. package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
  99. package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
  100. package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
  101. package/ios/internals/EnrichedTextInputViewState.h +20 -0
  102. package/ios/styles/BlockQuoteStyle.mm +248 -0
  103. package/ios/styles/BoldStyle.mm +122 -0
  104. package/ios/styles/H1Style.mm +10 -0
  105. package/ios/styles/H2Style.mm +10 -0
  106. package/ios/styles/H3Style.mm +10 -0
  107. package/ios/styles/HeadingStyleBase.mm +144 -0
  108. package/ios/styles/InlineCodeStyle.mm +163 -0
  109. package/ios/styles/ItalicStyle.mm +110 -0
  110. package/ios/styles/LinkStyle.mm +463 -0
  111. package/ios/styles/MentionStyle.mm +476 -0
  112. package/ios/styles/OrderedListStyle.mm +225 -0
  113. package/ios/styles/StrikethroughStyle.mm +80 -0
  114. package/ios/styles/UnderlineStyle.mm +112 -0
  115. package/ios/styles/UnorderedListStyle.mm +225 -0
  116. package/ios/utils/BaseStyleProtocol.h +16 -0
  117. package/ios/utils/ColorExtension.h +6 -0
  118. package/ios/utils/ColorExtension.mm +27 -0
  119. package/ios/utils/FontExtension.h +13 -0
  120. package/ios/utils/FontExtension.mm +91 -0
  121. package/ios/utils/LayoutManagerExtension.h +6 -0
  122. package/ios/utils/LayoutManagerExtension.mm +171 -0
  123. package/ios/utils/LinkData.h +9 -0
  124. package/ios/utils/LinkData.mm +4 -0
  125. package/ios/utils/MentionParams.h +9 -0
  126. package/ios/utils/MentionParams.mm +4 -0
  127. package/ios/utils/MentionStyleProps.h +13 -0
  128. package/ios/utils/MentionStyleProps.mm +56 -0
  129. package/ios/utils/OccurenceUtils.h +37 -0
  130. package/ios/utils/OccurenceUtils.mm +124 -0
  131. package/ios/utils/ParagraphsUtils.h +7 -0
  132. package/ios/utils/ParagraphsUtils.mm +54 -0
  133. package/ios/utils/StringExtension.h +15 -0
  134. package/ios/utils/StringExtension.mm +57 -0
  135. package/ios/utils/StyleHeaders.h +74 -0
  136. package/ios/utils/StylePair.h +9 -0
  137. package/ios/utils/StylePair.mm +4 -0
  138. package/ios/utils/StyleTypeEnum.h +22 -0
  139. package/ios/utils/TextDecorationLineEnum.h +6 -0
  140. package/ios/utils/TextDecorationLineEnum.mm +4 -0
  141. package/ios/utils/TextInsertionUtils.h +6 -0
  142. package/ios/utils/TextInsertionUtils.mm +48 -0
  143. package/ios/utils/WordsUtils.h +6 -0
  144. package/ios/utils/WordsUtils.mm +88 -0
  145. package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
  146. package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
  147. package/lib/module/EnrichedTextInput.js +191 -0
  148. package/lib/module/EnrichedTextInput.js.map +1 -0
  149. package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
  150. package/lib/module/index.js +4 -0
  151. package/lib/module/index.js.map +1 -0
  152. package/lib/module/normalizeHtmlStyle.js +141 -0
  153. package/lib/module/normalizeHtmlStyle.js.map +1 -0
  154. package/lib/module/package.json +1 -0
  155. package/lib/typescript/package.json +1 -0
  156. package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
  157. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
  158. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
  159. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
  160. package/lib/typescript/src/index.d.ts +3 -0
  161. package/lib/typescript/src/index.d.ts.map +1 -0
  162. package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
  163. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
  164. package/package.json +172 -1
  165. package/react-native.config.js +13 -0
  166. package/src/EnrichedTextInput.tsx +358 -0
  167. package/src/EnrichedTextInputNativeComponent.ts +235 -0
  168. package/src/index.tsx +9 -0
  169. package/src/normalizeHtmlStyle.ts +188 -0
@@ -0,0 +1,1190 @@
1
+ #import "EnrichedTextInputView.h"
2
+ #import "RCTFabricComponentsPlugins.h"
3
+ #import <ReactNativeEnriched/EnrichedTextInputViewComponentDescriptor.h>
4
+ #import <ReactNativeEnriched/EventEmitters.h>
5
+ #import <ReactNativeEnriched/Props.h>
6
+ #import <ReactNativeEnriched/RCTComponentViewHelpers.h>
7
+ #import <react/utils/ManagedObjectWrapper.h>
8
+ #import <folly/dynamic.h>
9
+ #import "UIView+React.h"
10
+ #import "StringExtension.h"
11
+ #import "CoreText/CoreText.h"
12
+ #import <React/RCTConversions.h>
13
+ #import "StyleHeaders.h"
14
+ #import "WordsUtils.h"
15
+ #import "LayoutManagerExtension.h"
16
+ #import "ZeroWidthSpaceUtils.h"
17
+
18
+ using namespace facebook::react;
19
+
20
+ @interface EnrichedTextInputView () <RCTEnrichedTextInputViewViewProtocol, UITextViewDelegate, NSObject>
21
+
22
+ @end
23
+
24
+ @implementation EnrichedTextInputView {
25
+ EnrichedTextInputViewShadowNode::ConcreteState::Shared _state;
26
+ int _componentViewHeightUpdateCounter;
27
+ NSMutableSet<NSNumber *> *_activeStyles;
28
+ NSDictionary<NSNumber *, NSArray<NSNumber *> *> *_conflictingStyles;
29
+ NSDictionary<NSNumber *, NSArray<NSNumber *> *> *_blockingStyles;
30
+ LinkData *_recentlyActiveLinkData;
31
+ NSRange _recentlyActiveLinkRange;
32
+ NSString *_recentlyEmittedString;
33
+ MentionParams *_recentlyActiveMentionParams;
34
+ NSRange _recentlyActiveMentionRange;
35
+ NSString *_recentlyEmittedHtml;
36
+ UILabel *_placeholderLabel;
37
+ UIColor *_placeholderColor;
38
+ BOOL _emitFocusBlur;
39
+ }
40
+
41
+ // MARK: - Component utils
42
+
43
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
44
+ return concreteComponentDescriptorProvider<EnrichedTextInputViewComponentDescriptor>();
45
+ }
46
+
47
+ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
48
+ return EnrichedTextInputView.class;
49
+ }
50
+
51
+ + (BOOL)shouldBeRecycled {
52
+ return NO;
53
+ }
54
+
55
+ // MARK: - Init
56
+
57
+ - (instancetype)initWithFrame:(CGRect)frame {
58
+ if (self = [super initWithFrame:frame]) {
59
+ static const auto defaultProps = std::make_shared<const EnrichedTextInputViewProps>();
60
+ _props = defaultProps;
61
+ [self setDefaults];
62
+ [self setupTextView];
63
+ [self setupPlaceholderLabel];
64
+ self.contentView = textView;
65
+ }
66
+ return self;
67
+ }
68
+
69
+ - (void)setDefaults {
70
+ _componentViewHeightUpdateCounter = 0;
71
+ _activeStyles = [[NSMutableSet alloc] init];
72
+ _recentlyActiveLinkRange = NSMakeRange(0, 0);
73
+ _recentlyActiveMentionRange = NSMakeRange(0, 0);
74
+ recentlyChangedRange = NSMakeRange(0, 0);
75
+ _recentlyEmittedString = @"";
76
+ _recentlyEmittedHtml = @"";
77
+ emitHtml = NO;
78
+ blockEmitting = NO;
79
+ _emitFocusBlur = YES;
80
+
81
+ defaultTypingAttributes = [[NSMutableDictionary<NSAttributedStringKey, id> alloc] init];
82
+
83
+ stylesDict = @{
84
+ @([BoldStyle getStyleType]) : [[BoldStyle alloc] initWithInput:self],
85
+ @([ItalicStyle getStyleType]): [[ItalicStyle alloc] initWithInput:self],
86
+ @([UnderlineStyle getStyleType]): [[UnderlineStyle alloc] initWithInput:self],
87
+ @([StrikethroughStyle getStyleType]): [[StrikethroughStyle alloc] initWithInput:self],
88
+ @([InlineCodeStyle getStyleType]): [[InlineCodeStyle alloc] initWithInput:self],
89
+ @([LinkStyle getStyleType]): [[LinkStyle alloc] initWithInput:self],
90
+ @([MentionStyle getStyleType]): [[MentionStyle alloc] initWithInput:self],
91
+ @([H1Style getStyleType]): [[H1Style alloc] initWithInput:self],
92
+ @([H2Style getStyleType]): [[H2Style alloc] initWithInput:self],
93
+ @([H3Style getStyleType]): [[H3Style alloc] initWithInput:self],
94
+ @([UnorderedListStyle getStyleType]): [[UnorderedListStyle alloc] initWithInput:self],
95
+ @([OrderedListStyle getStyleType]): [[OrderedListStyle alloc] initWithInput:self],
96
+ @([BlockQuoteStyle getStyleType]): [[BlockQuoteStyle alloc] initWithInput:self]
97
+ };
98
+
99
+ _conflictingStyles = @{
100
+ @([BoldStyle getStyleType]) : @[],
101
+ @([ItalicStyle getStyleType]) : @[],
102
+ @([UnderlineStyle getStyleType]) : @[],
103
+ @([StrikethroughStyle getStyleType]) : @[],
104
+ @([InlineCodeStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])],
105
+ @([LinkStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), @([MentionStyle getStyleType])],
106
+ @([MentionStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])],
107
+ @([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
108
+ @([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
109
+ @([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
110
+ @([UnorderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
111
+ @([OrderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
112
+ @([BlockQuoteStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType])]
113
+ };
114
+
115
+ _blockingStyles = @{
116
+ @([BoldStyle getStyleType]) : @[],
117
+ @([ItalicStyle getStyleType]) : @[],
118
+ @([UnderlineStyle getStyleType]) : @[],
119
+ @([StrikethroughStyle getStyleType]) : @[],
120
+ @([InlineCodeStyle getStyleType]) : @[],
121
+ @([LinkStyle getStyleType]): @[],
122
+ @([MentionStyle getStyleType]): @[],
123
+ @([H1Style getStyleType]): @[],
124
+ @([H2Style getStyleType]): @[],
125
+ @([H3Style getStyleType]): @[],
126
+ @([UnorderedListStyle getStyleType]): @[],
127
+ @([OrderedListStyle getStyleType]): @[],
128
+ @([BlockQuoteStyle getStyleType]): @[],
129
+ };
130
+
131
+ parser = [[InputParser alloc] initWithInput:self];
132
+ }
133
+
134
+ - (void)setupTextView {
135
+ textView = [[InputTextView alloc] init];
136
+ textView.backgroundColor = UIColor.clearColor;
137
+ textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
138
+ textView.textContainer.lineFragmentPadding = 0;
139
+ textView.delegate = self;
140
+ textView.input = self;
141
+ textView.layoutManager.input = self;
142
+ }
143
+
144
+ - (void)setupPlaceholderLabel {
145
+ _placeholderLabel = [[UILabel alloc] initWithFrame:CGRectZero];
146
+ _placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO;
147
+ [textView addSubview:_placeholderLabel];
148
+ [NSLayoutConstraint activateConstraints: @[
149
+ [_placeholderLabel.leadingAnchor constraintEqualToAnchor:textView.leadingAnchor],
150
+ [_placeholderLabel.widthAnchor constraintEqualToAnchor:textView.widthAnchor],
151
+ [_placeholderLabel.topAnchor constraintEqualToAnchor:textView.topAnchor],
152
+ [_placeholderLabel.bottomAnchor constraintEqualToAnchor:textView.bottomAnchor]
153
+ ]];
154
+ _placeholderLabel.lineBreakMode = NSLineBreakByTruncatingTail;
155
+ _placeholderLabel.text = @"";
156
+ _placeholderLabel.hidden = YES;
157
+ }
158
+
159
+ // MARK: - Props
160
+
161
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
162
+ const auto &oldViewProps = *std::static_pointer_cast<EnrichedTextInputViewProps const>(_props);
163
+ const auto &newViewProps = *std::static_pointer_cast<EnrichedTextInputViewProps const>(props);
164
+ BOOL isFirstMount = NO;
165
+ BOOL stylePropChanged = NO;
166
+
167
+ // initial config
168
+ if(config == nullptr) {
169
+ isFirstMount = YES;
170
+ config = [[InputConfig alloc] init];
171
+ }
172
+
173
+ // any style prop changes:
174
+ // firstly we create the new config for the changes
175
+
176
+ InputConfig *newConfig = [config copy];
177
+
178
+ if(newViewProps.color != oldViewProps.color) {
179
+ if(isColorMeaningful(newViewProps.color)) {
180
+ UIColor *uiColor = RCTUIColorFromSharedColor(newViewProps.color);
181
+ [newConfig setPrimaryColor:uiColor];
182
+ } else {
183
+ [newConfig setPrimaryColor:nullptr];
184
+ }
185
+ stylePropChanged = YES;
186
+ }
187
+
188
+ if(newViewProps.fontSize != oldViewProps.fontSize) {
189
+ if(newViewProps.fontSize) {
190
+ NSNumber* fontSize = @(newViewProps.fontSize);
191
+ [newConfig setPrimaryFontSize:fontSize];
192
+ } else {
193
+ [newConfig setPrimaryFontSize:nullptr];
194
+ }
195
+ stylePropChanged = YES;
196
+ }
197
+
198
+ if(newViewProps.fontWeight != oldViewProps.fontWeight) {
199
+ if(!newViewProps.fontWeight.empty()) {
200
+ [newConfig setPrimaryFontWeight:[NSString fromCppString:newViewProps.fontWeight]];
201
+ } else {
202
+ [newConfig setPrimaryFontWeight:nullptr];
203
+ }
204
+ stylePropChanged = YES;
205
+ }
206
+
207
+ if(newViewProps.fontFamily != oldViewProps.fontFamily) {
208
+ if(!newViewProps.fontFamily.empty()) {
209
+ [newConfig setPrimaryFontFamily:[NSString fromCppString:newViewProps.fontFamily]];
210
+ } else {
211
+ [newConfig setPrimaryFontFamily:nullptr];
212
+ }
213
+ stylePropChanged = YES;
214
+ }
215
+
216
+ // rich text style
217
+
218
+ if(newViewProps.htmlStyle.h1.fontSize != oldViewProps.htmlStyle.h1.fontSize) {
219
+ [newConfig setH1FontSize:newViewProps.htmlStyle.h1.fontSize];
220
+ stylePropChanged = YES;
221
+ }
222
+
223
+ if(newViewProps.htmlStyle.h1.bold != oldViewProps.htmlStyle.h1.bold) {
224
+ [newConfig setH1Bold:newViewProps.htmlStyle.h1.bold];
225
+ stylePropChanged = YES;
226
+ }
227
+
228
+ if(newViewProps.htmlStyle.h2.fontSize != oldViewProps.htmlStyle.h2.fontSize) {
229
+ [newConfig setH2FontSize:newViewProps.htmlStyle.h2.fontSize];
230
+ stylePropChanged = YES;
231
+ }
232
+
233
+ if(newViewProps.htmlStyle.h2.bold != oldViewProps.htmlStyle.h2.bold) {
234
+ [newConfig setH2Bold:newViewProps.htmlStyle.h2.bold];
235
+ stylePropChanged = YES;
236
+ }
237
+
238
+ if(newViewProps.htmlStyle.h3.fontSize != oldViewProps.htmlStyle.h3.fontSize) {
239
+ [newConfig setH3FontSize:newViewProps.htmlStyle.h3.fontSize];
240
+ stylePropChanged = YES;
241
+ }
242
+
243
+ if(newViewProps.htmlStyle.h3.bold != oldViewProps.htmlStyle.h3.bold) {
244
+ [newConfig setH3Bold:newViewProps.htmlStyle.h3.bold];
245
+ stylePropChanged = YES;
246
+ }
247
+
248
+ if(newViewProps.htmlStyle.blockquote.borderColor != oldViewProps.htmlStyle.blockquote.borderColor) {
249
+ if(isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) {
250
+ [newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.blockquote.borderColor)];
251
+ stylePropChanged = YES;
252
+ }
253
+ }
254
+
255
+ if(newViewProps.htmlStyle.blockquote.borderWidth != oldViewProps.htmlStyle.blockquote.borderWidth) {
256
+ [newConfig setBlockquoteBorderWidth:newViewProps.htmlStyle.blockquote.borderWidth];
257
+ stylePropChanged = YES;
258
+ }
259
+
260
+ if(newViewProps.htmlStyle.blockquote.gapWidth != oldViewProps.htmlStyle.blockquote.gapWidth) {
261
+ [newConfig setBlockquoteGapWidth:newViewProps.htmlStyle.blockquote.gapWidth];
262
+ stylePropChanged = YES;
263
+ }
264
+
265
+ // since this prop defaults to undefined on JS side, we need to force set the value on first mount
266
+ if(newViewProps.htmlStyle.blockquote.color != oldViewProps.htmlStyle.blockquote.color || isFirstMount) {
267
+ if(isColorMeaningful(newViewProps.htmlStyle.blockquote.color)) {
268
+ [newConfig setBlockquoteColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.blockquote.color)];
269
+ } else {
270
+ [newConfig setBlockquoteColor:[newConfig primaryColor]];
271
+ }
272
+ stylePropChanged = YES;
273
+ }
274
+
275
+ if(newViewProps.htmlStyle.code.color != oldViewProps.htmlStyle.code.color) {
276
+ if(isColorMeaningful(newViewProps.htmlStyle.code.color)) {
277
+ [newConfig setInlineCodeFgColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.code.color)];
278
+ stylePropChanged = YES;
279
+ }
280
+ }
281
+
282
+ if(newViewProps.htmlStyle.code.backgroundColor != oldViewProps.htmlStyle.code.backgroundColor) {
283
+ if(isColorMeaningful(newViewProps.htmlStyle.code.backgroundColor)) {
284
+ [newConfig setInlineCodeBgColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.code.backgroundColor)];
285
+ stylePropChanged = YES;
286
+ }
287
+ }
288
+
289
+ if(newViewProps.htmlStyle.ol.gapWidth != oldViewProps.htmlStyle.ol.gapWidth) {
290
+ [newConfig setOrderedListGapWidth:newViewProps.htmlStyle.ol.gapWidth];
291
+ stylePropChanged = YES;
292
+ }
293
+
294
+ if(newViewProps.htmlStyle.ol.marginLeft != oldViewProps.htmlStyle.ol.marginLeft) {
295
+ [newConfig setOrderedListMarginLeft:newViewProps.htmlStyle.ol.marginLeft];
296
+ stylePropChanged = YES;
297
+ }
298
+
299
+ // since this prop defaults to undefined on JS side, we need to force set the value on first mount
300
+ if(newViewProps.htmlStyle.ol.markerFontWeight != oldViewProps.htmlStyle.ol.markerFontWeight || isFirstMount) {
301
+ if(!newViewProps.htmlStyle.ol.markerFontWeight.empty()) {
302
+ [newConfig setOrderedListMarkerFontWeight:[NSString fromCppString: newViewProps.htmlStyle.ol.markerFontWeight]];
303
+ } else {
304
+ [newConfig setOrderedListMarkerFontWeight:[newConfig primaryFontWeight]];
305
+ }
306
+ stylePropChanged = YES;
307
+ }
308
+
309
+ // since this prop defaults to undefined on JS side, we need to force set the value on first mount
310
+ if(newViewProps.htmlStyle.ol.markerColor != oldViewProps.htmlStyle.ol.markerColor || isFirstMount) {
311
+ if(isColorMeaningful(newViewProps.htmlStyle.ol.markerColor)) {
312
+ [newConfig setOrderedListMarkerColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.ol.markerColor)];
313
+ } else {
314
+ [newConfig setOrderedListMarkerColor:[newConfig primaryColor]];
315
+ }
316
+ stylePropChanged = YES;
317
+ }
318
+
319
+ if(newViewProps.htmlStyle.ul.bulletColor != oldViewProps.htmlStyle.ul.bulletColor) {
320
+ if(isColorMeaningful(newViewProps.htmlStyle.ul.bulletColor)) {
321
+ [newConfig setUnorderedListBulletColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.ul.bulletColor)];
322
+ stylePropChanged = YES;
323
+ }
324
+ }
325
+
326
+ if(newViewProps.htmlStyle.ul.bulletSize != oldViewProps.htmlStyle.ul.bulletSize) {
327
+ [newConfig setUnorderedListBulletSize:newViewProps.htmlStyle.ul.bulletSize];
328
+ stylePropChanged = YES;
329
+ }
330
+
331
+ if(newViewProps.htmlStyle.ul.gapWidth != oldViewProps.htmlStyle.ul.gapWidth) {
332
+ [newConfig setUnorderedListGapWidth:newViewProps.htmlStyle.ul.gapWidth];
333
+ stylePropChanged = YES;
334
+ }
335
+
336
+ if(newViewProps.htmlStyle.ul.marginLeft != oldViewProps.htmlStyle.ul.marginLeft) {
337
+ [newConfig setUnorderedListMarginLeft:newViewProps.htmlStyle.ul.marginLeft];
338
+ stylePropChanged = YES;
339
+ }
340
+
341
+ if(newViewProps.htmlStyle.a.color != oldViewProps.htmlStyle.a.color) {
342
+ if(isColorMeaningful(newViewProps.htmlStyle.a.color)) {
343
+ [newConfig setLinkColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.a.color)];
344
+ stylePropChanged = YES;
345
+ }
346
+ }
347
+
348
+ if(newViewProps.htmlStyle.a.textDecorationLine != oldViewProps.htmlStyle.a.textDecorationLine) {
349
+ NSString *objcString = [NSString fromCppString:newViewProps.htmlStyle.a.textDecorationLine];
350
+ if([objcString isEqualToString:DecorationUnderline]) {
351
+ [newConfig setLinkDecorationLine:DecorationUnderline];
352
+ } else {
353
+ // both DecorationNone and a different, wrong value gets a DecorationNone here
354
+ [newConfig setLinkDecorationLine:DecorationNone];
355
+ }
356
+ stylePropChanged = YES;
357
+ }
358
+
359
+ folly::dynamic oldMentionStyle = oldViewProps.htmlStyle.mention;
360
+ folly::dynamic newMentionStyle = newViewProps.htmlStyle.mention;
361
+ if(oldMentionStyle != newMentionStyle) {
362
+ bool newSingleProps = NO;
363
+
364
+ for(const auto& obj : newMentionStyle.items()) {
365
+ if(obj.second.isInt() || obj.second.isString()) {
366
+ // we are in just a single MentionStyleProps object
367
+ newSingleProps = YES;
368
+ break;
369
+ } else if(obj.second.isObject()) {
370
+ // we are in map of indicators to MentionStyleProps
371
+ newSingleProps = NO;
372
+ break;
373
+ }
374
+ }
375
+
376
+ if(newSingleProps) {
377
+ [newConfig setMentionStyleProps:[MentionStyleProps getSinglePropsFromFollyDynamic:newMentionStyle]];
378
+ } else {
379
+ [newConfig setMentionStyleProps:[MentionStyleProps getComplexPropsFromFollyDynamic:newMentionStyle]];
380
+ }
381
+
382
+ stylePropChanged = YES;
383
+ }
384
+
385
+ if(stylePropChanged) {
386
+ // all the text needs to be rebuilt
387
+ // we get the current html using old config, then switch to new config and replace text using the html
388
+ // this way, the newest config attributes are being used!
389
+
390
+ // the html needs to be generated using the old config
391
+ NSString *currentHtml = [parser parseToHtmlFromRange:NSMakeRange(0, textView.textStorage.string.length)];
392
+
393
+ // now set the new config
394
+ config = newConfig;
395
+
396
+ // we don't want to emit these html changes in here
397
+ BOOL prevEmitHtml = emitHtml;
398
+ if(prevEmitHtml) {
399
+ emitHtml = NO;
400
+ }
401
+
402
+ // make sure everything is sound in the html
403
+ NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:currentHtml];
404
+ if(initiallyProcessedHtml != nullptr) {
405
+ [parser replaceWholeFromHtml:initiallyProcessedHtml];
406
+ }
407
+
408
+ if(prevEmitHtml) {
409
+ emitHtml = YES;
410
+ }
411
+
412
+ // fill the typing attributes with style props
413
+ defaultTypingAttributes[NSForegroundColorAttributeName] = [config primaryColor];
414
+ defaultTypingAttributes[NSFontAttributeName] = [config primaryFont];
415
+ defaultTypingAttributes[NSUnderlineColorAttributeName] = [config primaryColor];
416
+ defaultTypingAttributes[NSStrikethroughColorAttributeName] = [config primaryColor];
417
+ defaultTypingAttributes[NSParagraphStyleAttributeName] = [[NSParagraphStyle alloc] init];
418
+ textView.typingAttributes = defaultTypingAttributes;
419
+
420
+ // update the placeholder as well
421
+ [self refreshPlaceholderLabelStyles];
422
+ }
423
+
424
+ // editable
425
+ if(newViewProps.editable != oldViewProps.editable) {
426
+ textView.editable = newViewProps.editable;
427
+ }
428
+
429
+ // default value - must be set before placeholder to make sure it correctly shows on first mount
430
+ if(newViewProps.defaultValue != oldViewProps.defaultValue) {
431
+ NSString *newDefaultValue = [NSString fromCppString:newViewProps.defaultValue];
432
+
433
+ NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:newDefaultValue];
434
+ if(initiallyProcessedHtml == nullptr) {
435
+ // just plain text
436
+ textView.text = newDefaultValue;
437
+ } else {
438
+ // we've got some seemingly proper html
439
+ [parser replaceWholeFromHtml:initiallyProcessedHtml];
440
+ }
441
+ }
442
+
443
+ // placeholderTextColor
444
+ if(newViewProps.placeholderTextColor != oldViewProps.placeholderTextColor) {
445
+ // some real color
446
+ if(isColorMeaningful(newViewProps.placeholderTextColor)) {
447
+ _placeholderColor = RCTUIColorFromSharedColor(newViewProps.placeholderTextColor);
448
+ } else {
449
+ _placeholderColor = nullptr;
450
+ }
451
+ [self refreshPlaceholderLabelStyles];
452
+ }
453
+
454
+ // placeholder
455
+ if(newViewProps.placeholder != oldViewProps.placeholder) {
456
+ _placeholderLabel.text = [NSString fromCppString:newViewProps.placeholder];
457
+ [self refreshPlaceholderLabelStyles];
458
+ // additionally show placeholder on first mount if it should be there
459
+ if(isFirstMount && textView.text.length == 0) {
460
+ [self setPlaceholderLabelShown:YES];
461
+ }
462
+ }
463
+
464
+ // mention indicators
465
+ auto mismatchPair = std::mismatch(
466
+ newViewProps.mentionIndicators.begin(), newViewProps.mentionIndicators.end(),
467
+ oldViewProps.mentionIndicators.begin(), oldViewProps.mentionIndicators.end()
468
+ );
469
+ if(mismatchPair.first != newViewProps.mentionIndicators.end() || mismatchPair.second != oldViewProps.mentionIndicators.end()) {
470
+ NSMutableSet<NSNumber *> *newIndicators = [[NSMutableSet alloc] init];
471
+ for(const std::string &item : newViewProps.mentionIndicators) {
472
+ if(item.length() == 1) {
473
+ [newIndicators addObject:@(item[0])];
474
+ }
475
+ }
476
+ [config setMentionIndicators:newIndicators];
477
+ }
478
+
479
+ // selection color sets both selection and cursor on iOS (just as in RN)
480
+ if(newViewProps.selectionColor != oldViewProps.selectionColor) {
481
+ if(isColorMeaningful(newViewProps.selectionColor)) {
482
+ textView.tintColor = RCTUIColorFromSharedColor(newViewProps.selectionColor);
483
+ } else {
484
+ textView.tintColor = nullptr;
485
+ }
486
+ }
487
+
488
+ // autoCapitalize
489
+ if(newViewProps.autoCapitalize != oldViewProps.autoCapitalize) {
490
+ NSString *str = [NSString fromCppString:newViewProps.autoCapitalize];
491
+ if([str isEqualToString: @"none"]) {
492
+ textView.autocapitalizationType = UITextAutocapitalizationTypeNone;
493
+ } else if([str isEqualToString: @"sentences"]) {
494
+ textView.autocapitalizationType = UITextAutocapitalizationTypeSentences;
495
+ } else if([str isEqualToString: @"words"]) {
496
+ textView.autocapitalizationType = UITextAutocapitalizationTypeWords;
497
+ } else if([str isEqualToString: @"characters"]) {
498
+ textView.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
499
+ }
500
+
501
+ // textView needs to be refocused on autocapitalization type change and we don't want to emit these events
502
+ if([textView isFirstResponder]) {
503
+ _emitFocusBlur = NO;
504
+ [textView reactBlur];
505
+ [textView reactFocus];
506
+ _emitFocusBlur = YES;
507
+ }
508
+ }
509
+
510
+ // isOnChangeHtmlSet
511
+ emitHtml = newViewProps.isOnChangeHtmlSet;
512
+
513
+ [super updateProps:props oldProps:oldProps];
514
+ // mandatory text and height checks
515
+ [self anyTextMayHaveBeenModified];
516
+ [self tryUpdatingHeight];
517
+
518
+ // autofocus - needs to be done at the very end
519
+ if(isFirstMount && newViewProps.autoFocus) {
520
+ [textView reactFocus];
521
+ }
522
+ }
523
+
524
+ - (void)setPlaceholderLabelShown:(BOOL)shown {
525
+ if(shown) {
526
+ [self refreshPlaceholderLabelStyles];
527
+ _placeholderLabel.hidden = NO;
528
+ } else {
529
+ _placeholderLabel.hidden = YES;
530
+ }
531
+ }
532
+
533
+ - (void)refreshPlaceholderLabelStyles {
534
+ NSMutableDictionary *newAttrs = [defaultTypingAttributes mutableCopy];
535
+ if(_placeholderColor != nullptr) {
536
+ newAttrs[NSForegroundColorAttributeName] = _placeholderColor;
537
+ }
538
+ NSAttributedString *newAttrStr = [[NSAttributedString alloc] initWithString:_placeholderLabel.text attributes: newAttrs];
539
+ _placeholderLabel.attributedText = newAttrStr;
540
+ }
541
+
542
+ // MARK: - Measuring and states
543
+
544
+ - (CGSize)measureSize:(CGFloat)maxWidth {
545
+ // copy the the whole attributed string
546
+ NSMutableAttributedString *currentStr = [[NSMutableAttributedString alloc] initWithAttributedString:textView.textStorage];
547
+
548
+ // edge case: empty input should still be of a height of a single line, so we add a mock "I" character
549
+ if([currentStr length] == 0 ) {
550
+ [currentStr appendAttributedString:
551
+ [[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
552
+ ];
553
+ }
554
+
555
+ // edge case: input with only a zero width space should still be of a height of a single line, so we add a mock "I" character
556
+ if([currentStr length] == 1 && [[currentStr.string substringWithRange:NSMakeRange(0, 1)] isEqualToString:@"\u200B"]) {
557
+ [currentStr appendAttributedString:
558
+ [[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
559
+ ];
560
+ }
561
+
562
+ // edge case: trailing newlines aren't counted towards height calculations, so we add a mock "I" character
563
+ if(currentStr.length > 0) {
564
+ unichar lastChar = [currentStr.string characterAtIndex:currentStr.length-1];
565
+ if([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
566
+ [currentStr appendAttributedString:
567
+ [[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
568
+ ];
569
+ }
570
+ }
571
+
572
+ CGRect boundingBox = [currentStr boundingRectWithSize:
573
+ CGSizeMake(maxWidth, CGFLOAT_MAX)
574
+ options: NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
575
+ context: nullptr
576
+ ];
577
+
578
+ return CGSizeMake(maxWidth, ceil(boundingBox.size.height));
579
+ }
580
+
581
+ // make sure the newest state is kept in _state property
582
+ - (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState {
583
+ _state = std::static_pointer_cast<const EnrichedTextInputViewShadowNode::ConcreteState>(state);
584
+
585
+ // first render with all the needed stuff already defined (state and componentView)
586
+ // so we need to run a single height calculation for any initial values
587
+ if(oldState == nullptr) {
588
+ [self tryUpdatingHeight];
589
+ }
590
+ }
591
+
592
+ - (void)tryUpdatingHeight {
593
+ if(_state == nullptr) {
594
+ return;
595
+ }
596
+ _componentViewHeightUpdateCounter++;
597
+ auto selfRef = wrapManagedObjectWeakly(self);
598
+ _state->updateState(EnrichedTextInputViewState(_componentViewHeightUpdateCounter, selfRef));
599
+ }
600
+
601
+ // MARK: - Active styles
602
+
603
+ - (void)tryUpdatingActiveStyles {
604
+ // style updates are emitted only if something differs from the previously active styles
605
+ BOOL updateNeeded = NO;
606
+
607
+ // data for onLinkDetected event
608
+ LinkData *detectedLinkData;
609
+ NSRange detectedLinkRange = NSMakeRange(0, 0);
610
+
611
+ // data for onMentionDetected event
612
+ MentionParams *detectedMentionParams;
613
+ NSRange detectedMentionRange = NSMakeRange(0, 0);
614
+
615
+ for (NSNumber* type in stylesDict) {
616
+ id<BaseStyleProtocol> style = stylesDict[type];
617
+ BOOL wasActive = [_activeStyles containsObject: type];
618
+ BOOL isActive = [style detectStyle:textView.selectedRange];
619
+ if(wasActive != isActive) {
620
+ updateNeeded = YES;
621
+ if(isActive) {
622
+ [_activeStyles addObject:type];
623
+ } else {
624
+ [_activeStyles removeObject:type];
625
+ }
626
+ }
627
+
628
+ // onLinkDetected event
629
+ if(isActive && [type intValue] == [LinkStyle getStyleType]) {
630
+ // get the link data
631
+ LinkData *candidateLinkData;
632
+ NSRange candidateLinkRange = NSMakeRange(0, 0);
633
+ LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
634
+ if(linkStyleClass != nullptr) {
635
+ candidateLinkData = [linkStyleClass getLinkDataAt:textView.selectedRange.location];
636
+ candidateLinkRange = [linkStyleClass getFullLinkRangeAt:textView.selectedRange.location];
637
+ }
638
+
639
+ if(wasActive == NO) {
640
+ // we changed selection from non-link to a link
641
+ detectedLinkData = candidateLinkData;
642
+ detectedLinkRange = candidateLinkRange;
643
+ } else if(
644
+ ![_recentlyActiveLinkData.url isEqualToString:candidateLinkData.url] ||
645
+ ![_recentlyActiveLinkData.text isEqualToString:candidateLinkData.text] ||
646
+ !NSEqualRanges(_recentlyActiveLinkRange, candidateLinkRange)
647
+ ) {
648
+ // we changed selection from one link to the other or modified current link's text
649
+ detectedLinkData = candidateLinkData;
650
+ detectedLinkRange = candidateLinkRange;
651
+ }
652
+ }
653
+
654
+ // onMentionDetected event
655
+ if(isActive && [type intValue] == [MentionStyle getStyleType]) {
656
+ // get mention data
657
+ MentionParams *candidateMentionParams;
658
+ NSRange candidateMentionRange = NSMakeRange(0, 0);
659
+ MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
660
+ if(mentionStyleClass != nullptr) {
661
+ candidateMentionParams = [mentionStyleClass getMentionParamsAt:textView.selectedRange.location];
662
+ candidateMentionRange = [mentionStyleClass getFullMentionRangeAt:textView.selectedRange.location];
663
+ }
664
+
665
+ if(wasActive == NO) {
666
+ // selection was changed from a non-mention to a mention
667
+ detectedMentionParams = candidateMentionParams;
668
+ detectedMentionRange = candidateMentionRange;
669
+ } else if(
670
+ ![_recentlyActiveMentionParams.text isEqualToString:candidateMentionParams.text] ||
671
+ ![_recentlyActiveMentionParams.attributes isEqualToString:candidateMentionParams.attributes] ||
672
+ !NSEqualRanges(_recentlyActiveMentionRange, candidateMentionRange)
673
+ ) {
674
+ // selection changed from one mention to another
675
+ detectedMentionParams = candidateMentionParams;
676
+ detectedMentionRange = candidateMentionRange;
677
+ }
678
+ }
679
+ }
680
+
681
+ if(updateNeeded) {
682
+ auto emitter = [self getEventEmitter];
683
+ if(emitter != nullptr) {
684
+ emitter->onChangeState({
685
+ .isBold = [_activeStyles containsObject: @([BoldStyle getStyleType])],
686
+ .isItalic = [_activeStyles containsObject: @([ItalicStyle getStyleType])],
687
+ .isUnderline = [_activeStyles containsObject: @([UnderlineStyle getStyleType])],
688
+ .isStrikeThrough = [_activeStyles containsObject: @([StrikethroughStyle getStyleType])],
689
+ .isInlineCode = [_activeStyles containsObject: @([InlineCodeStyle getStyleType])],
690
+ .isLink = [_activeStyles containsObject: @([LinkStyle getStyleType])],
691
+ .isMention = [_activeStyles containsObject: @([MentionStyle getStyleType])],
692
+ .isH1 = [_activeStyles containsObject: @([H1Style getStyleType])],
693
+ .isH2 = [_activeStyles containsObject: @([H2Style getStyleType])],
694
+ .isH3 = [_activeStyles containsObject: @([H3Style getStyleType])],
695
+ .isUnorderedList = [_activeStyles containsObject: @([UnorderedListStyle getStyleType])],
696
+ .isOrderedList = [_activeStyles containsObject: @([OrderedListStyle getStyleType])],
697
+ .isBlockQuote = [_activeStyles containsObject: @([BlockQuoteStyle getStyleType])],
698
+ .isCodeBlock = NO, // [_activeStyles containsObject: @([CodeBlockStyle getStyleType])],
699
+ .isImage = NO // [_activeStyles containsObject: @([ImageStyle getStyleType]])],
700
+ });
701
+ }
702
+ }
703
+
704
+ if(detectedLinkData != nullptr) {
705
+ // emit onLinkeDetected event
706
+ [self emitOnLinkDetectedEvent:detectedLinkData.text url:detectedLinkData.url range:detectedLinkRange];
707
+
708
+ _recentlyActiveLinkData = detectedLinkData;
709
+ _recentlyActiveLinkRange = detectedLinkRange;
710
+ }
711
+
712
+ if(detectedMentionParams != nullptr) {
713
+ // emit onMentionDetected event
714
+ [self emitOnMentionDetectedEvent:detectedMentionParams.text indicator:detectedMentionParams.indicator attributes:detectedMentionParams.attributes];
715
+
716
+ _recentlyActiveMentionParams = detectedMentionParams;
717
+ _recentlyActiveMentionRange = detectedMentionRange;
718
+ }
719
+
720
+ // emit onChangeHtml event if needed
721
+ [self tryEmittingOnChangeHtmlEvent];
722
+ }
723
+
724
+ // MARK: - Native commands and events
725
+
726
+ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
727
+ if([commandName isEqualToString:@"focus"]) {
728
+ [self focus];
729
+ } else if([commandName isEqualToString:@"blur"]) {
730
+ [self blur];
731
+ } else if([commandName isEqualToString:@"setValue"]) {
732
+ NSString *value = (NSString *)args[0];
733
+ [self setValue:value];
734
+ } else if([commandName isEqualToString:@"toggleBold"]) {
735
+ [self toggleRegularStyle: [BoldStyle getStyleType]];
736
+ } else if([commandName isEqualToString:@"toggleItalic"]) {
737
+ [self toggleRegularStyle: [ItalicStyle getStyleType]];
738
+ } else if([commandName isEqualToString:@"toggleUnderline"]) {
739
+ [self toggleRegularStyle: [UnderlineStyle getStyleType]];
740
+ } else if([commandName isEqualToString:@"toggleStrikeThrough"]) {
741
+ [self toggleRegularStyle: [StrikethroughStyle getStyleType]];
742
+ } else if([commandName isEqualToString:@"toggleInlineCode"]) {
743
+ [self toggleRegularStyle: [InlineCodeStyle getStyleType]];
744
+ } else if([commandName isEqualToString:@"addLink"]) {
745
+ NSInteger start = [((NSNumber*)args[0]) integerValue];
746
+ NSInteger end = [((NSNumber*)args[1]) integerValue];
747
+ NSString *text = (NSString *)args[2];
748
+ NSString *url = (NSString *)args[3];
749
+ [self addLinkAt:start end:end text:text url:url];
750
+ } else if([commandName isEqualToString:@"addMention"]) {
751
+ NSString *indicator = (NSString *)args[0];
752
+ NSString *text = (NSString *)args[1];
753
+ NSString *attributes = (NSString *)args[2];
754
+ [self addMention:indicator text:text attributes:attributes];
755
+ } else if([commandName isEqualToString:@"startMention"]) {
756
+ NSString *indicator = (NSString *)args[0];
757
+ [self startMentionWithIndicator:indicator];
758
+ } else if([commandName isEqualToString:@"toggleH1"]) {
759
+ [self toggleParagraphStyle:[H1Style getStyleType]];
760
+ } else if([commandName isEqualToString:@"toggleH2"]) {
761
+ [self toggleParagraphStyle:[H2Style getStyleType]];
762
+ } else if([commandName isEqualToString:@"toggleH3"]) {
763
+ [self toggleParagraphStyle:[H3Style getStyleType]];
764
+ } else if([commandName isEqualToString:@"toggleUnorderedList"]) {
765
+ [self toggleParagraphStyle:[UnorderedListStyle getStyleType]];
766
+ } else if([commandName isEqualToString:@"toggleOrderedList"]) {
767
+ [self toggleParagraphStyle:[OrderedListStyle getStyleType]];
768
+ } else if([commandName isEqualToString:@"toggleBlockQuote"]) {
769
+ [self toggleParagraphStyle:[BlockQuoteStyle getStyleType]];
770
+ }
771
+ }
772
+
773
+ - (std::shared_ptr<EnrichedTextInputViewEventEmitter>)getEventEmitter {
774
+ if(_eventEmitter != nullptr && !blockEmitting) {
775
+ auto emitter = static_cast<const EnrichedTextInputViewEventEmitter &>(*_eventEmitter);
776
+ return std::make_shared<EnrichedTextInputViewEventEmitter>(emitter);
777
+ } else {
778
+ return nullptr;
779
+ }
780
+ }
781
+
782
+ - (void)blur {
783
+ [textView reactBlur];
784
+ }
785
+
786
+ - (void)focus {
787
+ [textView reactFocus];
788
+ }
789
+
790
+ - (void)setValue:(NSString *)value {
791
+ NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:value];
792
+ if(initiallyProcessedHtml == nullptr) {
793
+ // just plain text
794
+ textView.text = value;
795
+ } else {
796
+ // we've got some seemingly proper html
797
+ [parser replaceWholeFromHtml:initiallyProcessedHtml];
798
+ }
799
+
800
+ // set recentlyChangedRange and check for changes
801
+ recentlyChangedRange = NSMakeRange(0, textView.textStorage.string.length);
802
+ [self anyTextMayHaveBeenModified];
803
+ }
804
+
805
+ - (void)emitOnLinkDetectedEvent:(NSString *)text url:(NSString *)url range:(NSRange)range {
806
+ auto emitter = [self getEventEmitter];
807
+ if(emitter != nullptr) {
808
+ emitter->onLinkDetected({
809
+ .text = [text toCppString],
810
+ .url = [url toCppString],
811
+ .start = static_cast<int>(range.location),
812
+ .end = static_cast<int>(range.location + range.length),
813
+ });
814
+ }
815
+ }
816
+
817
+ - (void)emitOnMentionDetectedEvent:(NSString *)text indicator:(NSString *)indicator attributes:(NSString *)attributes {
818
+ auto emitter = [self getEventEmitter];
819
+ if(emitter != nullptr) {
820
+ emitter->onMentionDetected({
821
+ .text = [text toCppString],
822
+ .indicator = [indicator toCppString],
823
+ .payload = [attributes toCppString]
824
+ });
825
+ }
826
+ }
827
+
828
+ - (void)emitOnMentionEvent:(NSString *)indicator text:(NSString *)text {
829
+ auto emitter = [self getEventEmitter];
830
+ if(emitter != nullptr) {
831
+ if(text != nullptr) {
832
+ folly::dynamic fdStr = [text toCppString];
833
+ emitter->onMention({
834
+ .indicator = [indicator toCppString],
835
+ .text = fdStr
836
+ });
837
+ } else {
838
+ folly::dynamic nul = nullptr;
839
+ emitter->onMention({
840
+ .indicator = [indicator toCppString],
841
+ .text = nul
842
+ });
843
+ }
844
+ }
845
+ }
846
+
847
+ - (void)tryEmittingOnChangeHtmlEvent {
848
+ if(!emitHtml || textView.markedTextRange != nullptr) {
849
+ return;
850
+ }
851
+ auto emitter = [self getEventEmitter];
852
+ if(emitter != nullptr) {
853
+ NSString *htmlOutput = [parser parseToHtmlFromRange:NSMakeRange(0, textView.textStorage.string.length)];
854
+ // make sure html really changed
855
+ if(![htmlOutput isEqualToString:_recentlyEmittedHtml]) {
856
+ _recentlyEmittedHtml = htmlOutput;
857
+ emitter->onChangeHtml({
858
+ .value = [htmlOutput toCppString]
859
+ });
860
+ }
861
+ }
862
+ }
863
+
864
+ // MARK: - Styles manipulation
865
+
866
+ - (void)toggleRegularStyle:(StyleType)type {
867
+ id<BaseStyleProtocol> styleClass = stylesDict[@(type)];
868
+
869
+ if([self handleStyleBlocksAndConflicts:type range:textView.selectedRange]) {
870
+ [styleClass applyStyle:textView.selectedRange];
871
+ [self anyTextMayHaveBeenModified];
872
+ }
873
+ }
874
+
875
+ - (void)toggleParagraphStyle:(StyleType)type {
876
+ id<BaseStyleProtocol> styleClass = stylesDict[@(type)];
877
+ // we always pass whole paragraph/s range to these styles
878
+ NSRange paragraphRange = [textView.textStorage.string paragraphRangeForRange:textView.selectedRange];
879
+
880
+ if([self handleStyleBlocksAndConflicts:type range:paragraphRange]) {
881
+ [styleClass applyStyle:paragraphRange];
882
+ [self anyTextMayHaveBeenModified];
883
+ }
884
+ }
885
+
886
+ - (void)addLinkAt:(NSInteger)start end:(NSInteger)end text:(NSString *)text url:(NSString *)url {
887
+ LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
888
+ if(linkStyleClass == nullptr) { return; }
889
+
890
+ // translate the output start-end notation to range
891
+ NSRange linkRange = NSMakeRange(start, end - start);
892
+ if([self handleStyleBlocksAndConflicts:[LinkStyle getStyleType] range:linkRange]) {
893
+ [linkStyleClass addLink:text url:url range:linkRange manual:YES];
894
+ [self anyTextMayHaveBeenModified];
895
+ }
896
+ }
897
+
898
+ - (void)addMention:(NSString *)indicator text:(NSString *)text attributes:(NSString *)attributes {
899
+ MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
900
+ if(mentionStyleClass == nullptr) { return; }
901
+ if([mentionStyleClass getActiveMentionRange] == nullptr) { return; }
902
+
903
+ if([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType] range:[[mentionStyleClass getActiveMentionRange] rangeValue]]) {
904
+ [mentionStyleClass addMention:indicator text:text attributes:attributes];
905
+ [self anyTextMayHaveBeenModified];
906
+ }
907
+ }
908
+
909
+ - (void)startMentionWithIndicator:(NSString *)indicator {
910
+ MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
911
+ if(mentionStyleClass == nullptr) { return; }
912
+
913
+ if([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType] range:[[mentionStyleClass getActiveMentionRange] rangeValue]]) {
914
+ [mentionStyleClass startMentionWithIndicator:indicator];
915
+ [self anyTextMayHaveBeenModified];
916
+ }
917
+ }
918
+
919
+ // returns false when style shouldn't be applied and true when it can be
920
+ - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range {
921
+ // handle blocking styles: if any is present we do not apply the toggled style
922
+ NSArray<NSNumber *> *blocking = [self getPresentStyleTypesFrom: _blockingStyles[@(type)] range:range];
923
+ if(blocking.count != 0) {
924
+ return NO;
925
+ }
926
+
927
+ // handle conflicting styles: all of their occurences have to be removed
928
+ NSArray<NSNumber *> *conflicting = [self getPresentStyleTypesFrom: _conflictingStyles[@(type)] range:range];
929
+ if(conflicting.count != 0) {
930
+ for(NSNumber *style in conflicting) {
931
+ id<BaseStyleProtocol> styleClass = stylesDict[style];
932
+
933
+ if(range.length >= 1) {
934
+ // for ranges, we need to remove each occurence
935
+ NSArray<StylePair *> *allOccurences = [styleClass findAllOccurences:range];
936
+
937
+ for(StylePair* pair in allOccurences) {
938
+ [styleClass removeAttributes: [pair.rangeValue rangeValue]];
939
+ }
940
+ } else {
941
+ // with in-place selection, we just remove the adequate typing attributes
942
+ [styleClass removeTypingAttributes];
943
+ }
944
+ }
945
+ }
946
+ return YES;
947
+ }
948
+
949
+ - (NSArray<NSNumber *> *)getPresentStyleTypesFrom:(NSArray<NSNumber *> *)types range:(NSRange)range {
950
+ NSMutableArray<NSNumber *> *resultArray = [[NSMutableArray<NSNumber *> alloc] init];
951
+ for(NSNumber *type in types) {
952
+ id<BaseStyleProtocol> styleClass = stylesDict[type];
953
+
954
+ if(range.length >= 1) {
955
+ if([styleClass anyOccurence:range]) {
956
+ [resultArray addObject:type];
957
+ }
958
+ } else {
959
+ if([styleClass detectStyle:range]) {
960
+ [resultArray addObject:type];
961
+ }
962
+ }
963
+ }
964
+ return resultArray;
965
+ }
966
+
967
+ - (void)manageSelectionBasedChanges {
968
+ // link typing attributes fix
969
+ LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
970
+ if(linkStyleClass != nullptr) {
971
+ [linkStyleClass manageLinkTypingAttributes];
972
+ }
973
+
974
+ // mention typing attribtues fix and active editing
975
+ MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
976
+ if(mentionStyleClass != nullptr) {
977
+ [mentionStyleClass manageMentionTypingAttributes];
978
+ [mentionStyleClass manageMentionEditing];
979
+ }
980
+ }
981
+
982
+ - (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)range {
983
+ // manual links refreshing and automatic links detection handling
984
+ LinkStyle* linkStyle = [stylesDict objectForKey:@([LinkStyle getStyleType])];
985
+ if(linkStyle != nullptr) {
986
+ // manual links need to be handled first because they can block automatic links after being refreshed
987
+ [linkStyle handleManualLinks:word inRange:range];
988
+ [linkStyle handleAutomaticLinks:word inRange:range];
989
+ }
990
+ }
991
+
992
+ - (void)anyTextMayHaveBeenModified {
993
+ // we don't do no text changes when working with iOS marked text
994
+ if(textView.markedTextRange != nullptr) {
995
+ return;
996
+ }
997
+
998
+ // emptying input typing attributes management
999
+ if(textView.textStorage.string.length == 0 && _recentlyEmittedString.length > 0) {
1000
+ // reset typing attribtues
1001
+ textView.typingAttributes = defaultTypingAttributes;
1002
+ }
1003
+
1004
+ // zero width space removal
1005
+ [ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
1006
+
1007
+ // inline code on newlines fix
1008
+ InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle getStyleType])];
1009
+ if(codeStyle != nullptr) {
1010
+ [codeStyle handleNewlines];
1011
+ }
1012
+
1013
+ // blockquote colors management
1014
+ BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
1015
+ if(bqStyle != nullptr) {
1016
+ [bqStyle manageBlockquoteColor];
1017
+ }
1018
+
1019
+ // placholder management
1020
+ if(!_placeholderLabel.hidden && textView.textStorage.string.length > 0) {
1021
+ [self setPlaceholderLabelShown:NO];
1022
+ } else if(textView.textStorage.string.length == 0 && _placeholderLabel.hidden) {
1023
+ [self setPlaceholderLabelShown:YES];
1024
+ }
1025
+
1026
+ if(![textView.textStorage.string isEqualToString:_recentlyEmittedString]) {
1027
+ // mentions removal management
1028
+ MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
1029
+ if(mentionStyleClass != nullptr) {
1030
+ [mentionStyleClass handleExistingMentions];
1031
+ }
1032
+
1033
+ // modified words handling
1034
+ NSArray *modifiedWords = [WordsUtils getAffectedWordsFromText:textView.textStorage.string modificationRange:recentlyChangedRange];
1035
+ if(modifiedWords != nullptr) {
1036
+ for(NSDictionary *wordDict in modifiedWords) {
1037
+ NSString *wordText = (NSString *)[wordDict objectForKey:@"word"];
1038
+ NSValue *wordRange = (NSValue *)[wordDict objectForKey:@"range"];
1039
+
1040
+ if(wordText == nullptr || wordRange == nullptr) {
1041
+ continue;
1042
+ }
1043
+
1044
+ [self handleWordModificationBasedChanges:wordText inRange:[wordRange rangeValue]];
1045
+ }
1046
+ }
1047
+
1048
+ // emit string without zero width spaces
1049
+ NSString *stringToBeEmitted = [[textView.textStorage.string stringByReplacingOccurrencesOfString:@"\u200B" withString:@""] copy];
1050
+
1051
+ // emit onChangeText event
1052
+ auto emitter = [self getEventEmitter];
1053
+ if(emitter != nullptr) {
1054
+ emitter->onChangeText({
1055
+ .value = [stringToBeEmitted toCppString]
1056
+ });
1057
+ }
1058
+
1059
+ // set the recently emitted string
1060
+ _recentlyEmittedString = stringToBeEmitted;
1061
+ }
1062
+
1063
+ // update height on each character change
1064
+ [self tryUpdatingHeight];
1065
+ // update active styles as well
1066
+ [self tryUpdatingActiveStyles];
1067
+ // update drawing
1068
+ NSRange wholeRange = NSMakeRange(0, textView.textStorage.string.length);
1069
+ [textView.layoutManager invalidateLayoutForCharacterRange:wholeRange actualCharacterRange:nullptr];
1070
+ [textView.layoutManager invalidateDisplayForCharacterRange:wholeRange];
1071
+ }
1072
+
1073
+ // MARK: - UITextView delegate methods
1074
+
1075
+ - (void)textViewDidBeginEditing:(UITextView *)textView {
1076
+ auto emitter = [self getEventEmitter];
1077
+ if(emitter != nullptr) {
1078
+ //send onFocus event if allowed
1079
+ if(_emitFocusBlur) {
1080
+ emitter->onInputFocus({});
1081
+ }
1082
+
1083
+ NSString *textAtSelection = [[[NSMutableString alloc] initWithString:textView.textStorage.string] substringWithRange: textView.selectedRange];
1084
+ emitter->onChangeSelection({
1085
+ .start = static_cast<int>(textView.selectedRange.location),
1086
+ .end = static_cast<int>(textView.selectedRange.location + textView.selectedRange.length),
1087
+ .text = [textAtSelection toCppString]
1088
+ });
1089
+ }
1090
+ // manage selection changes since textViewDidChangeSelection sometimes doesn't run on focus
1091
+ [self manageSelectionBasedChanges];
1092
+ }
1093
+
1094
+ - (void)textViewDidEndEditing:(UITextView *)textView {
1095
+ auto emitter = [self getEventEmitter];
1096
+ if(emitter != nullptr && _emitFocusBlur) {
1097
+ //send onBlur event
1098
+ emitter->onInputBlur({});
1099
+ }
1100
+ }
1101
+
1102
+ - (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
1103
+ recentlyChangedRange = NSMakeRange(range.location, text.length);
1104
+
1105
+ BOOL rejectTextChanges = NO;
1106
+
1107
+ UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
1108
+ if(uStyle != nullptr) {
1109
+ // removing first line list fix
1110
+ BOOL removedFirstLineList = [uStyle handleBackspaceInRange:range replacementText:text];
1111
+
1112
+ // creating unordered list from "- "
1113
+ BOOL addedShortcutList = [uStyle tryHandlingListShorcutInRange:range replacementText:text];
1114
+
1115
+ // any of these changes make us reject text changes
1116
+ rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
1117
+ }
1118
+
1119
+ OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
1120
+ if(oStyle != nullptr) {
1121
+ // removing first line list fix
1122
+ BOOL removedFirstLineList = [oStyle handleBackspaceInRange:range replacementText:text];
1123
+
1124
+ // creating ordered list from "1."
1125
+ BOOL addedShortcutList = [oStyle tryHandlingListShorcutInRange:range replacementText:text];
1126
+
1127
+ // any of these changes make us reject text changes
1128
+ rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
1129
+ }
1130
+
1131
+ BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
1132
+ if(bqStyle != nullptr) {
1133
+ // removing first line quote fix
1134
+ BOOL removedFirstLineQuote = [bqStyle handleBackspaceInRange:range replacementText:text];
1135
+ rejectTextChanges = rejectTextChanges || removedFirstLineQuote;
1136
+ }
1137
+
1138
+ LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
1139
+ if(linkStyle != nullptr) {
1140
+ // persisting link attributes fix
1141
+ BOOL fixedLeadingAttributes = [linkStyle handleLeadingLinkReplacement:range replacementText:text];
1142
+ rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
1143
+ }
1144
+
1145
+ MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
1146
+ if(mentionStyle != nullptr) {
1147
+ // persisting mention attributes fix
1148
+ BOOL fixedLeadingAttributes = [mentionStyle handleLeadingMentionReplacement:range replacementText:text];
1149
+ rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
1150
+ }
1151
+
1152
+ // zero width space removal fix
1153
+ rejectTextChanges = rejectTextChanges || [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self];
1154
+
1155
+ if(rejectTextChanges) {
1156
+ [self anyTextMayHaveBeenModified];
1157
+ }
1158
+
1159
+ return !rejectTextChanges;
1160
+ }
1161
+
1162
+ - (void)textViewDidChangeSelection:(UITextView *)textView {
1163
+ // emit the event
1164
+ NSString *textAtSelection = [[[NSMutableString alloc] initWithString:textView.textStorage.string] substringWithRange: textView.selectedRange];
1165
+
1166
+ auto emitter = [self getEventEmitter];
1167
+ if(emitter != nullptr) {
1168
+ // iOS range works differently because it specifies location and length
1169
+ // here, start is the location, but end is the first index BEHIND the end. So a 0 length range will have equal start and end
1170
+ emitter->onChangeSelection({
1171
+ .start = static_cast<int>(textView.selectedRange.location),
1172
+ .end = static_cast<int>(textView.selectedRange.location + textView.selectedRange.length),
1173
+ .text = [textAtSelection toCppString]
1174
+ });
1175
+ }
1176
+
1177
+ // manage selection changes
1178
+ [self manageSelectionBasedChanges];
1179
+
1180
+ // update active styles
1181
+ [self tryUpdatingActiveStyles];
1182
+ }
1183
+
1184
+ // this function isn't called always when some text changes (for example setting link or starting mention with indicator doesn't fire it)
1185
+ // so all the logic is in anyTextMayHaveBeenModified
1186
+ - (void)textViewDidChange:(UITextView *)textView {
1187
+ [self anyTextMayHaveBeenModified];
1188
+ }
1189
+
1190
+ @end