react-native-advanced-text 0.1.22 → 0.1.24
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/ios/AdvancedTextView.mm +401 -227
- package/package.json +1 -1
package/ios/AdvancedTextView.mm
CHANGED
|
@@ -9,35 +9,92 @@
|
|
|
9
9
|
|
|
10
10
|
using namespace facebook::react;
|
|
11
11
|
|
|
12
|
-
@interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate>
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
// Forward declaration
|
|
14
|
+
@class AdvancedTextView;
|
|
15
|
+
|
|
16
|
+
@interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate, UITextViewDelegate>
|
|
17
|
+
|
|
15
18
|
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *wordRanges;
|
|
16
19
|
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIColor *> *highlightColors;
|
|
17
20
|
@property (nonatomic, strong) NSArray<NSString *> *menuOptions;
|
|
18
21
|
@property (nonatomic, assign) NSInteger indicatorWordIndex;
|
|
19
22
|
|
|
23
|
+
// ✅ ADD THIS LINE
|
|
24
|
+
- (void)handleCustomMenuAction:(UIMenuItem *)sender;
|
|
25
|
+
|
|
26
|
+
@end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// Custom UITextView subclass to override menu behavior
|
|
30
|
+
@interface CustomTextView : UITextView
|
|
31
|
+
@property (nonatomic, weak) AdvancedTextView *parentView;
|
|
32
|
+
@end
|
|
33
|
+
|
|
34
|
+
@implementation CustomTextView
|
|
35
|
+
|
|
36
|
+
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
|
37
|
+
{
|
|
38
|
+
NSLog(@"[CustomTextView] canPerformAction: %@", NSStringFromSelector(action));
|
|
39
|
+
|
|
40
|
+
// Only allow our custom menu actions
|
|
41
|
+
if (action == @selector(handleCustomMenuAction:)) {
|
|
42
|
+
NSLog(@"[CustomTextView] ✅ Allowing custom action");
|
|
43
|
+
return YES;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Block ALL system actions
|
|
47
|
+
NSLog(@"[CustomTextView] ❌ Blocking system action: %@", NSStringFromSelector(action));
|
|
48
|
+
return NO;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
- (void)handleCustomMenuAction:(UIMenuItem *)sender
|
|
52
|
+
{
|
|
53
|
+
// Forward to parent view
|
|
54
|
+
if (self.parentView) {
|
|
55
|
+
|
|
56
|
+
[self.parentView handleCustomMenuAction:sender];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate, UITextViewDelegate>
|
|
65
|
+
|
|
66
|
+
@property (nonatomic, strong) CustomTextView *textView;
|
|
67
|
+
|
|
20
68
|
@end
|
|
21
69
|
|
|
22
70
|
@implementation AdvancedTextView
|
|
23
71
|
|
|
24
72
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
25
73
|
{
|
|
74
|
+
NSLog(@"[AdvancedTextView] componentDescriptorProvider called");
|
|
26
75
|
return concreteComponentDescriptorProvider<AdvancedTextViewComponentDescriptor>();
|
|
27
76
|
}
|
|
28
77
|
|
|
29
78
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
30
79
|
{
|
|
80
|
+
NSLog(@"[AdvancedTextView] initWithFrame called");
|
|
31
81
|
if (self = [super initWithFrame:frame]) {
|
|
32
|
-
|
|
33
|
-
|
|
82
|
+
@try {
|
|
83
|
+
static const auto defaultProps = std::make_shared<const AdvancedTextViewProps>();
|
|
84
|
+
_props = defaultProps;
|
|
85
|
+
|
|
86
|
+
_wordRanges = [NSMutableArray array];
|
|
87
|
+
_highlightColors = [NSMutableDictionary dictionary];
|
|
88
|
+
_indicatorWordIndex = -1;
|
|
34
89
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
_indicatorWordIndex = -1;
|
|
90
|
+
[self setupTextView];
|
|
91
|
+
[self setupGestureRecognizers];
|
|
38
92
|
|
|
39
|
-
|
|
40
|
-
|
|
93
|
+
NSLog(@"[AdvancedTextView] Initialization successful");
|
|
94
|
+
} @catch (NSException *exception) {
|
|
95
|
+
NSLog(@"[AdvancedTextView] Exception in init: %@", exception);
|
|
96
|
+
@throw;
|
|
97
|
+
}
|
|
41
98
|
}
|
|
42
99
|
|
|
43
100
|
return self;
|
|
@@ -45,325 +102,437 @@ using namespace facebook::react;
|
|
|
45
102
|
|
|
46
103
|
- (void)setupTextView
|
|
47
104
|
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
105
|
+
NSLog(@"[AdvancedTextView] setupTextView called");
|
|
106
|
+
@try {
|
|
107
|
+
_textView = [[CustomTextView alloc] initWithFrame:self.bounds];
|
|
108
|
+
_textView.parentView = self;
|
|
109
|
+
_textView.editable = NO;
|
|
110
|
+
_textView.selectable = YES;
|
|
111
|
+
_textView.scrollEnabled = YES;
|
|
112
|
+
_textView.backgroundColor = [UIColor clearColor];
|
|
113
|
+
_textView.textContainerInset = UIEdgeInsetsMake(8, 8, 8, 8);
|
|
114
|
+
_textView.font = [UIFont systemFontOfSize:16];
|
|
115
|
+
_textView.textColor = [UIColor labelColor];
|
|
116
|
+
_textView.delegate = self;
|
|
117
|
+
|
|
118
|
+
self.contentView = _textView;
|
|
119
|
+
NSLog(@"[AdvancedTextView] TextView setup successful");
|
|
120
|
+
} @catch (NSException *exception) {
|
|
121
|
+
NSLog(@"[AdvancedTextView] Exception in setupTextView: %@", exception);
|
|
122
|
+
@throw;
|
|
123
|
+
}
|
|
57
124
|
}
|
|
58
125
|
|
|
59
126
|
- (void)setupGestureRecognizers
|
|
60
127
|
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
128
|
+
NSLog(@"[AdvancedTextView] setupGestureRecognizers called");
|
|
129
|
+
@try {
|
|
130
|
+
// Single tap for word selection
|
|
131
|
+
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]
|
|
132
|
+
initWithTarget:self
|
|
133
|
+
action:@selector(handleTap:)];
|
|
134
|
+
tapGesture.delegate = self;
|
|
135
|
+
[_textView addGestureRecognizer:tapGesture];
|
|
136
|
+
|
|
137
|
+
NSLog(@"[AdvancedTextView] Gesture recognizers setup successful");
|
|
138
|
+
} @catch (NSException *exception) {
|
|
139
|
+
NSLog(@"[AdvancedTextView] Exception in setupGestureRecognizers: %@", exception);
|
|
140
|
+
@throw;
|
|
141
|
+
}
|
|
74
142
|
}
|
|
75
143
|
|
|
76
144
|
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
|
77
145
|
{
|
|
78
|
-
|
|
79
|
-
|
|
146
|
+
NSLog(@"[AdvancedTextView] updateProps called");
|
|
147
|
+
@try {
|
|
148
|
+
const auto &oldViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(_props);
|
|
149
|
+
const auto &newViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(props);
|
|
150
|
+
|
|
151
|
+
BOOL textChanged = NO;
|
|
152
|
+
BOOL highlightsChanged = NO;
|
|
153
|
+
BOOL menuChanged = NO;
|
|
154
|
+
BOOL indicatorChanged = NO;
|
|
155
|
+
|
|
156
|
+
// Check text change
|
|
157
|
+
if (oldViewProps.text != newViewProps.text) {
|
|
158
|
+
textChanged = YES;
|
|
159
|
+
NSLog(@"[AdvancedTextView] Text changed");
|
|
160
|
+
}
|
|
80
161
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
162
|
+
// Check highlighted words change
|
|
163
|
+
if (oldViewProps.highlightedWords.size() != newViewProps.highlightedWords.size()) {
|
|
164
|
+
highlightsChanged = YES;
|
|
165
|
+
} else {
|
|
166
|
+
for (size_t i = 0; i < oldViewProps.highlightedWords.size(); i++) {
|
|
167
|
+
const auto &oldHW = oldViewProps.highlightedWords[i];
|
|
168
|
+
const auto &newHW = newViewProps.highlightedWords[i];
|
|
169
|
+
if (oldHW.index != newHW.index || oldHW.highlightColor != newHW.highlightColor) {
|
|
170
|
+
highlightsChanged = YES;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
86
175
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
176
|
+
// Check menu options change
|
|
177
|
+
if (oldViewProps.menuOptions.size() != newViewProps.menuOptions.size()) {
|
|
178
|
+
menuChanged = YES;
|
|
179
|
+
} else {
|
|
180
|
+
for (size_t i = 0; i < oldViewProps.menuOptions.size(); i++) {
|
|
181
|
+
if (oldViewProps.menuOptions[i] != newViewProps.menuOptions[i]) {
|
|
182
|
+
menuChanged = YES;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
91
187
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
188
|
+
// Check indicator change
|
|
189
|
+
if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
|
|
190
|
+
indicatorChanged = YES;
|
|
191
|
+
}
|
|
96
192
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
193
|
+
// Apply updates
|
|
194
|
+
if (textChanged) {
|
|
195
|
+
NSString *text = [NSString stringWithUTF8String:newViewProps.text.c_str()];
|
|
196
|
+
[self updateTextContent:text];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (highlightsChanged) {
|
|
200
|
+
[self updateHighlightedWords:newViewProps.highlightedWords];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (menuChanged) {
|
|
204
|
+
[self updateMenuOptions:newViewProps.menuOptions];
|
|
205
|
+
}
|
|
102
206
|
|
|
103
|
-
|
|
207
|
+
if (indicatorChanged) {
|
|
208
|
+
_indicatorWordIndex = newViewProps.indicatorWordIndex;
|
|
209
|
+
[self updateTextAppearance];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
[super updateProps:props oldProps:oldProps];
|
|
213
|
+
NSLog(@"[AdvancedTextView] updateProps completed successfully");
|
|
214
|
+
} @catch (NSException *exception) {
|
|
215
|
+
NSLog(@"[AdvancedTextView] Exception in updateProps: %@", exception.reason);
|
|
216
|
+
@throw;
|
|
217
|
+
}
|
|
104
218
|
}
|
|
105
219
|
|
|
106
220
|
- (void)updateTextContent:(NSString *)text
|
|
107
221
|
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
NSInteger wordIndex = 0;
|
|
115
|
-
while (searchRange.location < text.length) {
|
|
116
|
-
// Skip whitespace
|
|
117
|
-
while (searchRange.location < text.length &&
|
|
118
|
-
[whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
119
|
-
searchRange.location++;
|
|
120
|
-
searchRange.length = text.length - searchRange.location;
|
|
222
|
+
NSLog(@"[AdvancedTextView] updateTextContent called with text length: %lu",
|
|
223
|
+
(unsigned long)text.length);
|
|
224
|
+
@try {
|
|
225
|
+
if (!text) {
|
|
226
|
+
NSLog(@"[AdvancedTextView] Text is nil, skipping update");
|
|
227
|
+
return;
|
|
121
228
|
}
|
|
122
229
|
|
|
123
|
-
|
|
230
|
+
_textView.text = text;
|
|
124
231
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
while (searchRange.location < text.length &&
|
|
128
|
-
![whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
129
|
-
searchRange.location++;
|
|
130
|
-
}
|
|
232
|
+
// Parse text into words and their ranges
|
|
233
|
+
[_wordRanges removeAllObjects];
|
|
131
234
|
|
|
132
|
-
NSRange
|
|
133
|
-
|
|
235
|
+
NSRange searchRange = NSMakeRange(0, text.length);
|
|
236
|
+
NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
|
134
237
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
238
|
+
NSInteger wordIndex = 0;
|
|
239
|
+
while (searchRange.location < text.length) {
|
|
240
|
+
// Skip whitespace
|
|
241
|
+
while (searchRange.location < text.length &&
|
|
242
|
+
[whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
243
|
+
searchRange.location++;
|
|
244
|
+
searchRange.length = text.length - searchRange.location;
|
|
245
|
+
}
|
|
140
246
|
|
|
141
|
-
|
|
142
|
-
searchRange.length = text.length - searchRange.location;
|
|
143
|
-
}
|
|
247
|
+
if (searchRange.location >= text.length) break;
|
|
144
248
|
|
|
145
|
-
|
|
146
|
-
|
|
249
|
+
// Find word end
|
|
250
|
+
NSUInteger wordStart = searchRange.location;
|
|
251
|
+
while (searchRange.location < text.length &&
|
|
252
|
+
![whitespaceSet characterIsMember:[text characterAtIndex:searchRange.location]]) {
|
|
253
|
+
searchRange.location++;
|
|
254
|
+
}
|
|
147
255
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
[_highlightColors removeAllObjects];
|
|
256
|
+
NSRange wordRange = NSMakeRange(wordStart, searchRange.location - wordStart);
|
|
257
|
+
NSString *word = [text substringWithRange:wordRange];
|
|
151
258
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
259
|
+
[_wordRanges addObject:@{
|
|
260
|
+
@"word": word,
|
|
261
|
+
@"range": [NSValue valueWithRange:wordRange],
|
|
262
|
+
@"index": @(wordIndex)
|
|
263
|
+
}];
|
|
156
264
|
|
|
157
|
-
|
|
158
|
-
|
|
265
|
+
wordIndex++;
|
|
266
|
+
searchRange.length = text.length - searchRange.location;
|
|
159
267
|
}
|
|
268
|
+
|
|
269
|
+
NSLog(@"[AdvancedTextView] Parsed %ld words", (long)_wordRanges.count);
|
|
270
|
+
[self updateTextAppearance];
|
|
271
|
+
} @catch (NSException *exception) {
|
|
272
|
+
NSLog(@"[AdvancedTextView] Exception in updateTextContent: %@", exception);
|
|
273
|
+
@throw;
|
|
160
274
|
}
|
|
275
|
+
}
|
|
161
276
|
|
|
162
|
-
|
|
277
|
+
- (void)updateHighlightedWords:(const std::vector<AdvancedTextViewHighlightedWordsStruct> &)highlightedWords
|
|
278
|
+
{
|
|
279
|
+
NSLog(@"[AdvancedTextView] updateHighlightedWords called with %zu highlights",
|
|
280
|
+
highlightedWords.size());
|
|
281
|
+
@try {
|
|
282
|
+
[_highlightColors removeAllObjects];
|
|
283
|
+
|
|
284
|
+
for (const auto &hw : highlightedWords) {
|
|
285
|
+
NSInteger index = hw.index;
|
|
286
|
+
NSString *colorString = [NSString stringWithUTF8String:hw.highlightColor.c_str()];
|
|
287
|
+
UIColor *color = [self hexStringToColor:colorString];
|
|
288
|
+
|
|
289
|
+
if (color) {
|
|
290
|
+
_highlightColors[@(index)] = color;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
[self updateTextAppearance];
|
|
295
|
+
} @catch (NSException *exception) {
|
|
296
|
+
NSLog(@"[AdvancedTextView] Exception in updateHighlightedWords: %@", exception);
|
|
297
|
+
@throw;
|
|
298
|
+
}
|
|
163
299
|
}
|
|
164
300
|
|
|
165
301
|
- (void)updateMenuOptions:(const std::vector<std::string> &)options
|
|
166
302
|
{
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
303
|
+
NSLog(@"[AdvancedTextView] updateMenuOptions called with %zu options", options.size());
|
|
304
|
+
@try {
|
|
305
|
+
NSMutableArray *menuArray = [NSMutableArray array];
|
|
306
|
+
for (const auto &option : options) {
|
|
307
|
+
NSString *optionStr = [NSString stringWithUTF8String:option.c_str()];
|
|
308
|
+
[menuArray addObject:optionStr];
|
|
309
|
+
NSLog(@"[AdvancedTextView] Added menu option: %@", optionStr);
|
|
310
|
+
}
|
|
311
|
+
_menuOptions = [menuArray copy];
|
|
312
|
+
} @catch (NSException *exception) {
|
|
313
|
+
NSLog(@"[AdvancedTextView] Exception in updateMenuOptions: %@", exception);
|
|
314
|
+
@throw;
|
|
170
315
|
}
|
|
171
|
-
_menuOptions = [menuArray copy];
|
|
172
316
|
}
|
|
173
317
|
|
|
174
318
|
- (void)updateTextAppearance
|
|
175
319
|
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
initWithString:_textView.text];
|
|
180
|
-
|
|
181
|
-
// Apply default attributes
|
|
182
|
-
[attributedString addAttribute:NSFontAttributeName
|
|
183
|
-
value:[UIFont systemFontOfSize:16]
|
|
184
|
-
range:NSMakeRange(0, attributedString.length)];
|
|
185
|
-
|
|
186
|
-
[attributedString addAttribute:NSForegroundColorAttributeName
|
|
187
|
-
value:[UIColor labelColor]
|
|
188
|
-
range:NSMakeRange(0, attributedString.length)];
|
|
189
|
-
|
|
190
|
-
// Apply highlights
|
|
191
|
-
for (NSDictionary *wordInfo in _wordRanges) {
|
|
192
|
-
NSNumber *index = wordInfo[@"index"];
|
|
193
|
-
NSValue *rangeValue = wordInfo[@"range"];
|
|
194
|
-
NSRange range = [rangeValue rangeValue];
|
|
195
|
-
|
|
196
|
-
UIColor *highlightColor = _highlightColors[index];
|
|
197
|
-
if (highlightColor) {
|
|
198
|
-
[attributedString addAttribute:NSBackgroundColorAttributeName
|
|
199
|
-
value:highlightColor
|
|
200
|
-
range:range];
|
|
320
|
+
@try {
|
|
321
|
+
if (_wordRanges.count == 0 || !_textView.text || _textView.text.length == 0) {
|
|
322
|
+
return;
|
|
201
323
|
}
|
|
202
324
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
325
|
+
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
|
|
326
|
+
initWithString:_textView.text];
|
|
327
|
+
|
|
328
|
+
[attributedString addAttribute:NSFontAttributeName
|
|
329
|
+
value:[UIFont systemFontOfSize:16]
|
|
330
|
+
range:NSMakeRange(0, attributedString.length)];
|
|
331
|
+
|
|
332
|
+
[attributedString addAttribute:NSForegroundColorAttributeName
|
|
333
|
+
value:[UIColor labelColor]
|
|
334
|
+
range:NSMakeRange(0, attributedString.length)];
|
|
335
|
+
|
|
336
|
+
for (NSDictionary *wordInfo in _wordRanges) {
|
|
337
|
+
NSNumber *index = wordInfo[@"index"];
|
|
338
|
+
NSValue *rangeValue = wordInfo[@"range"];
|
|
339
|
+
NSRange range = [rangeValue rangeValue];
|
|
340
|
+
|
|
341
|
+
if (range.location + range.length > attributedString.length) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
UIColor *highlightColor = _highlightColors[index];
|
|
346
|
+
if (highlightColor) {
|
|
347
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName
|
|
348
|
+
value:highlightColor
|
|
349
|
+
range:range];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (_indicatorWordIndex >= 0 && [index integerValue] == _indicatorWordIndex) {
|
|
353
|
+
UIColor *indicatorColor = [[UIColor systemBlueColor] colorWithAlphaComponent:0.3];
|
|
354
|
+
[attributedString addAttribute:NSBackgroundColorAttributeName
|
|
355
|
+
value:indicatorColor
|
|
356
|
+
range:range];
|
|
357
|
+
}
|
|
211
358
|
}
|
|
212
|
-
}
|
|
213
359
|
|
|
214
|
-
|
|
360
|
+
_textView.attributedText = attributedString;
|
|
361
|
+
} @catch (NSException *exception) {
|
|
362
|
+
NSLog(@"[AdvancedTextView] Exception in updateTextAppearance: %@", exception.reason);
|
|
363
|
+
}
|
|
215
364
|
}
|
|
216
365
|
|
|
217
366
|
- (void)handleTap:(UITapGestureRecognizer *)gesture
|
|
218
367
|
{
|
|
219
|
-
|
|
368
|
+
@try {
|
|
369
|
+
if (gesture.state != UIGestureRecognizerStateEnded) return;
|
|
220
370
|
|
|
221
|
-
|
|
222
|
-
|
|
371
|
+
CGPoint location = [gesture locationInView:_textView];
|
|
372
|
+
NSInteger wordIndex = [self wordIndexAtPoint:location];
|
|
223
373
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
NSString *word = wordInfo[@"word"];
|
|
374
|
+
// Dismiss any existing selection
|
|
375
|
+
_textView.selectedTextRange = nil;
|
|
227
376
|
|
|
228
|
-
|
|
377
|
+
if (wordIndex >= 0 && wordIndex < _wordRanges.count) {
|
|
378
|
+
NSDictionary *wordInfo = _wordRanges[wordIndex];
|
|
379
|
+
NSString *word = wordInfo[@"word"];
|
|
380
|
+
|
|
381
|
+
[self emitWordPressEvent:word index:wordIndex];
|
|
382
|
+
}
|
|
383
|
+
} @catch (NSException *exception) {
|
|
384
|
+
NSLog(@"[AdvancedTextView] Exception in handleTap: %@", exception);
|
|
229
385
|
}
|
|
230
386
|
}
|
|
231
387
|
|
|
232
|
-
-
|
|
233
|
-
{
|
|
234
|
-
if (gesture.state != UIGestureRecognizerStateBegan) return;
|
|
388
|
+
#pragma mark - UITextViewDelegate
|
|
235
389
|
|
|
236
|
-
|
|
237
|
-
|
|
390
|
+
- (void)textViewDidChangeSelection:(UITextView *)textView
|
|
391
|
+
{
|
|
392
|
+
NSString *selectedText = [textView.text substringWithRange:textView.selectedRange];
|
|
238
393
|
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
NSString *word = wordInfo[@"word"];
|
|
394
|
+
if (selectedText.length > 0) {
|
|
395
|
+
NSLog(@"[AdvancedTextView] Selected text: %@", selectedText);
|
|
242
396
|
|
|
243
|
-
|
|
397
|
+
if (_menuOptions && _menuOptions.count > 0) {
|
|
398
|
+
[self setupCustomMenuItems];
|
|
399
|
+
}
|
|
244
400
|
}
|
|
245
401
|
}
|
|
246
402
|
|
|
247
|
-
- (
|
|
403
|
+
- (void)setupCustomMenuItems
|
|
248
404
|
{
|
|
249
|
-
|
|
250
|
-
point.x -= _textView.textContainerInset.left;
|
|
251
|
-
point.y -= _textView.textContainerInset.top;
|
|
405
|
+
NSLog(@"[AdvancedTextView] Setting up %lu custom menu items", (unsigned long)_menuOptions.count);
|
|
252
406
|
|
|
253
|
-
|
|
254
|
-
|
|
407
|
+
UIMenuController *menuController = [UIMenuController sharedMenuController];
|
|
408
|
+
NSMutableArray *customItems = [NSMutableArray array];
|
|
255
409
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
for (NSDictionary *wordInfo in _wordRanges) {
|
|
262
|
-
NSValue *rangeValue = wordInfo[@"range"];
|
|
263
|
-
NSRange range = [rangeValue rangeValue];
|
|
264
|
-
|
|
265
|
-
if (NSLocationInRange(characterIndex, range)) {
|
|
266
|
-
return [wordInfo[@"index"] integerValue];
|
|
267
|
-
}
|
|
410
|
+
for (NSString *option in _menuOptions) {
|
|
411
|
+
UIMenuItem *item = [[UIMenuItem alloc] initWithTitle:option
|
|
412
|
+
action:@selector(handleCustomMenuAction:)];
|
|
413
|
+
[customItems addObject:item];
|
|
414
|
+
NSLog(@"[AdvancedTextView] Created menu item: %@", option);
|
|
268
415
|
}
|
|
269
416
|
|
|
270
|
-
|
|
417
|
+
menuController.menuItems = customItems;
|
|
271
418
|
}
|
|
272
419
|
|
|
273
|
-
- (void)showContextMenuForWord:(NSString *)word atIndex:(NSInteger)index location:(CGPoint)location
|
|
274
|
-
{
|
|
275
|
-
if (!_menuOptions || _menuOptions.count == 0) return;
|
|
276
420
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
421
|
+
- (void)handleCustomMenuAction:(UIMenuItem *)sender
|
|
422
|
+
{
|
|
423
|
+
NSLog(@"[AdvancedTextView] Custom menu action: %@", sender.title);
|
|
280
424
|
|
|
281
|
-
|
|
282
|
-
UIAlertAction *action = [UIAlertAction actionWithTitle:option
|
|
283
|
-
style:UIAlertActionStyleDefault
|
|
284
|
-
handler:^(UIAlertAction * _Nonnull action) {
|
|
285
|
-
[self emitSelectionEvent:word menuOption:option];
|
|
286
|
-
}];
|
|
287
|
-
[alert addAction:action];
|
|
288
|
-
}
|
|
425
|
+
NSString *selectedText = [_textView.text substringWithRange:_textView.selectedRange];
|
|
289
426
|
|
|
290
|
-
|
|
291
|
-
style:UIAlertActionStyleCancel
|
|
292
|
-
handler:nil];
|
|
293
|
-
[alert addAction:cancelAction];
|
|
294
|
-
|
|
295
|
-
// Present from the root view controller
|
|
296
|
-
UIViewController *rootVC = [self findViewController];
|
|
297
|
-
if (rootVC) {
|
|
298
|
-
// For iPad, set up popover presentation
|
|
299
|
-
if (alert.popoverPresentationController) {
|
|
300
|
-
alert.popoverPresentationController.sourceView = _textView;
|
|
301
|
-
alert.popoverPresentationController.sourceRect = CGRectMake(location.x, location.y, 1, 1);
|
|
302
|
-
}
|
|
427
|
+
[self emitSelectionEvent:selectedText menuOption:sender.title];
|
|
303
428
|
|
|
304
|
-
|
|
305
|
-
|
|
429
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
430
|
+
self->_textView.selectedTextRange = nil;
|
|
431
|
+
NSLog(@"[AdvancedTextView] Selection cleared");
|
|
432
|
+
});
|
|
306
433
|
}
|
|
307
434
|
|
|
308
|
-
- (
|
|
435
|
+
- (NSInteger)wordIndexAtPoint:(CGPoint)point
|
|
309
436
|
{
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
437
|
+
@try {
|
|
438
|
+
if (!_textView.layoutManager || !_textView.textContainer) {
|
|
439
|
+
return -1;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
point.x -= _textView.textContainerInset.left;
|
|
443
|
+
point.y -= _textView.textContainerInset.top;
|
|
444
|
+
|
|
445
|
+
NSLayoutManager *layoutManager = _textView.layoutManager;
|
|
446
|
+
NSTextContainer *textContainer = _textView.textContainer;
|
|
447
|
+
|
|
448
|
+
NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
|
|
449
|
+
inTextContainer:textContainer
|
|
450
|
+
fractionOfDistanceBetweenInsertionPoints:nil];
|
|
451
|
+
|
|
452
|
+
for (NSDictionary *wordInfo in _wordRanges) {
|
|
453
|
+
NSValue *rangeValue = wordInfo[@"range"];
|
|
454
|
+
NSRange range = [rangeValue rangeValue];
|
|
455
|
+
|
|
456
|
+
if (NSLocationInRange(characterIndex, range)) {
|
|
457
|
+
return [wordInfo[@"index"] integerValue];
|
|
458
|
+
}
|
|
314
459
|
}
|
|
315
|
-
|
|
460
|
+
|
|
461
|
+
return -1;
|
|
462
|
+
} @catch (NSException *exception) {
|
|
463
|
+
return -1;
|
|
316
464
|
}
|
|
317
|
-
return nil;
|
|
318
465
|
}
|
|
319
466
|
|
|
320
467
|
- (void)emitWordPressEvent:(NSString *)word index:(NSInteger)index
|
|
321
468
|
{
|
|
322
|
-
|
|
323
|
-
|
|
469
|
+
NSLog(@"[AdvancedTextView] emitWordPressEvent: %@ at index: %ld", word, (long)index);
|
|
470
|
+
@try {
|
|
471
|
+
if (_eventEmitter) {
|
|
472
|
+
auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
|
|
324
473
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
474
|
+
AdvancedTextViewEventEmitter::OnWordPress event;
|
|
475
|
+
event.word = [word UTF8String];
|
|
476
|
+
event.index = static_cast<int>(index);
|
|
328
477
|
|
|
329
|
-
|
|
478
|
+
emitter->onWordPress(event);
|
|
479
|
+
}
|
|
480
|
+
} @catch (NSException *exception) {
|
|
481
|
+
NSLog(@"[AdvancedTextView] Exception in emitWordPressEvent: %@", exception);
|
|
330
482
|
}
|
|
331
483
|
}
|
|
332
484
|
|
|
333
485
|
- (void)emitSelectionEvent:(NSString *)selectedText menuOption:(NSString *)option
|
|
334
486
|
{
|
|
335
|
-
|
|
336
|
-
|
|
487
|
+
NSLog(@"[AdvancedTextView] emitSelectionEvent: %@ with option: %@", selectedText, option);
|
|
488
|
+
@try {
|
|
489
|
+
if (_eventEmitter) {
|
|
490
|
+
auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
|
|
337
491
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
492
|
+
AdvancedTextViewEventEmitter::OnSelection event;
|
|
493
|
+
event.selectedText = [selectedText UTF8String];
|
|
494
|
+
event.event = [option UTF8String];
|
|
341
495
|
|
|
342
|
-
|
|
496
|
+
emitter->onSelection(event);
|
|
497
|
+
}
|
|
498
|
+
} @catch (NSException *exception) {
|
|
499
|
+
NSLog(@"[AdvancedTextView] Exception in emitSelectionEvent: %@", exception);
|
|
343
500
|
}
|
|
344
501
|
}
|
|
345
502
|
|
|
346
503
|
- (void)layoutSubviews
|
|
347
504
|
{
|
|
348
|
-
|
|
349
|
-
|
|
505
|
+
@try {
|
|
506
|
+
[super layoutSubviews];
|
|
507
|
+
_textView.frame = self.bounds;
|
|
508
|
+
} @catch (NSException *exception) {
|
|
509
|
+
NSLog(@"[AdvancedTextView] Exception in layoutSubviews: %@", exception);
|
|
510
|
+
}
|
|
350
511
|
}
|
|
351
512
|
|
|
352
513
|
- (UIColor *)hexStringToColor:(NSString *)stringToConvert
|
|
353
514
|
{
|
|
354
|
-
|
|
515
|
+
@try {
|
|
516
|
+
if (!stringToConvert || [stringToConvert length] == 0) {
|
|
517
|
+
return nil;
|
|
518
|
+
}
|
|
355
519
|
|
|
356
|
-
|
|
357
|
-
|
|
520
|
+
NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
|
|
521
|
+
NSScanner *stringScanner = [NSScanner scannerWithString:noHashString];
|
|
358
522
|
|
|
359
|
-
|
|
360
|
-
|
|
523
|
+
unsigned hex;
|
|
524
|
+
if (![stringScanner scanHexInt:&hex]) {
|
|
525
|
+
return nil;
|
|
526
|
+
}
|
|
361
527
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
528
|
+
int r = (hex >> 16) & 0xFF;
|
|
529
|
+
int g = (hex >> 8) & 0xFF;
|
|
530
|
+
int b = (hex) & 0xFF;
|
|
365
531
|
|
|
366
|
-
|
|
532
|
+
return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f];
|
|
533
|
+
} @catch (NSException *exception) {
|
|
534
|
+
return nil;
|
|
535
|
+
}
|
|
367
536
|
}
|
|
368
537
|
|
|
369
538
|
Class<RCTComponentViewProtocol> AdvancedTextViewCls(void)
|
|
@@ -371,4 +540,9 @@ Class<RCTComponentViewProtocol> AdvancedTextViewCls(void)
|
|
|
371
540
|
return AdvancedTextView.class;
|
|
372
541
|
}
|
|
373
542
|
|
|
543
|
+
- (void)dealloc
|
|
544
|
+
{
|
|
545
|
+
NSLog(@"[AdvancedTextView] dealloc called");
|
|
546
|
+
}
|
|
547
|
+
|
|
374
548
|
@end
|
package/package.json
CHANGED