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.
@@ -9,35 +9,92 @@
9
9
 
10
10
  using namespace facebook::react;
11
11
 
12
- @interface AdvancedTextView () <RCTAdvancedTextViewViewProtocol, UIGestureRecognizerDelegate>
13
12
 
14
- @property (nonatomic, strong) UITextView *textView;
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
- static const auto defaultProps = std::make_shared<const AdvancedTextViewProps>();
33
- _props = defaultProps;
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
- _wordRanges = [NSMutableArray array];
36
- _highlightColors = [NSMutableDictionary dictionary];
37
- _indicatorWordIndex = -1;
90
+ [self setupTextView];
91
+ [self setupGestureRecognizers];
38
92
 
39
- [self setupTextView];
40
- [self setupGestureRecognizers];
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
- _textView = [[UITextView alloc] initWithFrame:self.bounds];
49
- _textView.editable = NO;
50
- _textView.scrollEnabled = YES;
51
- _textView.backgroundColor = [UIColor clearColor];
52
- _textView.textContainerInset = UIEdgeInsetsMake(8, 8, 8, 8);
53
- _textView.font = [UIFont systemFontOfSize:16];
54
- _textView.textColor = [UIColor labelColor];
55
-
56
- self.contentView = _textView;
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
- // Single tap for word selection
62
- UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]
63
- initWithTarget:self
64
- action:@selector(handleTap:)];
65
- tapGesture.delegate = self;
66
- [_textView addGestureRecognizer:tapGesture];
67
-
68
- // Long press for context menu
69
- UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc]
70
- initWithTarget:self
71
- action:@selector(handleLongPress:)];
72
- longPressGesture.delegate = self;
73
- [_textView addGestureRecognizer:longPressGesture];
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
- const auto &oldViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(_props);
79
- const auto &newViewProps = *std::static_pointer_cast<AdvancedTextViewProps const>(props);
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
- // Update text
82
- if (oldViewProps.text != newViewProps.text) {
83
- NSString *text = [NSString stringWithUTF8String:newViewProps.text.c_str()];
84
- [self updateTextContent:text];
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
- // Update highlighted words
88
- if (oldViewProps.highlightedWords != newViewProps.highlightedWords) {
89
- [self updateHighlightedWords:newViewProps.highlightedWords];
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
- // Update menu options
93
- if (oldViewProps.menuOptions != newViewProps.menuOptions) {
94
- [self updateMenuOptions:newViewProps.menuOptions];
95
- }
188
+ // Check indicator change
189
+ if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
190
+ indicatorChanged = YES;
191
+ }
96
192
 
97
- // Update indicator word index
98
- if (oldViewProps.indicatorWordIndex != newViewProps.indicatorWordIndex) {
99
- _indicatorWordIndex = newViewProps.indicatorWordIndex;
100
- [self updateTextAppearance];
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
- [super updateProps:props oldProps:oldProps];
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
- // Parse text into words and their ranges
109
- [_wordRanges removeAllObjects];
110
-
111
- NSRange searchRange = NSMakeRange(0, text.length);
112
- NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
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
- if (searchRange.location >= text.length) break;
230
+ _textView.text = text;
124
231
 
125
- // Find word end
126
- NSUInteger wordStart = searchRange.location;
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 wordRange = NSMakeRange(wordStart, searchRange.location - wordStart);
133
- NSString *word = [text substringWithRange:wordRange];
235
+ NSRange searchRange = NSMakeRange(0, text.length);
236
+ NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
134
237
 
135
- [_wordRanges addObject:@{
136
- @"word": word,
137
- @"range": [NSValue valueWithRange:wordRange],
138
- @"index": @(wordIndex)
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
- wordIndex++;
142
- searchRange.length = text.length - searchRange.location;
143
- }
247
+ if (searchRange.location >= text.length) break;
144
248
 
145
- [self updateTextAppearance];
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
- - (void)updateHighlightedWords:(const std::vector<AdvancedTextViewHighlightedWordsStruct> &)highlightedWords
149
- {
150
- [_highlightColors removeAllObjects];
256
+ NSRange wordRange = NSMakeRange(wordStart, searchRange.location - wordStart);
257
+ NSString *word = [text substringWithRange:wordRange];
151
258
 
152
- for (const auto &hw : highlightedWords) {
153
- NSInteger index = hw.index;
154
- NSString *colorString = [NSString stringWithUTF8String:hw.highlightColor.c_str()];
155
- UIColor *color = [self hexStringToColor:colorString];
259
+ [_wordRanges addObject:@{
260
+ @"word": word,
261
+ @"range": [NSValue valueWithRange:wordRange],
262
+ @"index": @(wordIndex)
263
+ }];
156
264
 
157
- if (color) {
158
- _highlightColors[@(index)] = color;
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
- [self updateTextAppearance];
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
- NSMutableArray *menuArray = [NSMutableArray array];
168
- for (const auto &option : options) {
169
- [menuArray addObject:[NSString stringWithUTF8String:option.c_str()]];
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
- if (_wordRanges.count == 0) return;
177
-
178
- NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
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
- // Add indicator (underline or special formatting) for indicated word
204
- if (_indicatorWordIndex >= 0 && [index integerValue] == _indicatorWordIndex) {
205
- [attributedString addAttribute:NSUnderlineStyleAttributeName
206
- value:@(NSUnderlineStyleSingle)
207
- range:range];
208
- [attributedString addAttribute:NSUnderlineColorAttributeName
209
- value:[UIColor systemBlueColor]
210
- range:range];
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
- _textView.attributedText = attributedString;
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
- if (gesture.state != UIGestureRecognizerStateEnded) return;
368
+ @try {
369
+ if (gesture.state != UIGestureRecognizerStateEnded) return;
220
370
 
221
- CGPoint location = [gesture locationInView:_textView];
222
- NSInteger wordIndex = [self wordIndexAtPoint:location];
371
+ CGPoint location = [gesture locationInView:_textView];
372
+ NSInteger wordIndex = [self wordIndexAtPoint:location];
223
373
 
224
- if (wordIndex >= 0 && wordIndex < _wordRanges.count) {
225
- NSDictionary *wordInfo = _wordRanges[wordIndex];
226
- NSString *word = wordInfo[@"word"];
374
+ // Dismiss any existing selection
375
+ _textView.selectedTextRange = nil;
227
376
 
228
- [self emitWordPressEvent:word index:wordIndex];
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
- - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
233
- {
234
- if (gesture.state != UIGestureRecognizerStateBegan) return;
388
+ #pragma mark - UITextViewDelegate
235
389
 
236
- CGPoint location = [gesture locationInView:_textView];
237
- NSInteger wordIndex = [self wordIndexAtPoint:location];
390
+ - (void)textViewDidChangeSelection:(UITextView *)textView
391
+ {
392
+ NSString *selectedText = [textView.text substringWithRange:textView.selectedRange];
238
393
 
239
- if (wordIndex >= 0 && wordIndex < _wordRanges.count) {
240
- NSDictionary *wordInfo = _wordRanges[wordIndex];
241
- NSString *word = wordInfo[@"word"];
394
+ if (selectedText.length > 0) {
395
+ NSLog(@"[AdvancedTextView] Selected text: %@", selectedText);
242
396
 
243
- [self showContextMenuForWord:word atIndex:wordIndex location:location];
397
+ if (_menuOptions && _menuOptions.count > 0) {
398
+ [self setupCustomMenuItems];
399
+ }
244
400
  }
245
401
  }
246
402
 
247
- - (NSInteger)wordIndexAtPoint:(CGPoint)point
403
+ - (void)setupCustomMenuItems
248
404
  {
249
- // Adjust point for text container insets
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
- NSLayoutManager *layoutManager = _textView.layoutManager;
254
- NSTextContainer *textContainer = _textView.textContainer;
407
+ UIMenuController *menuController = [UIMenuController sharedMenuController];
408
+ NSMutableArray *customItems = [NSMutableArray array];
255
409
 
256
- NSUInteger characterIndex = [layoutManager characterIndexForPoint:point
257
- inTextContainer:textContainer
258
- fractionOfDistanceBetweenInsertionPoints:nil];
259
-
260
- // Find which word this character belongs to
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
- return -1;
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
- UIAlertController *alert = [UIAlertController alertControllerWithTitle:word
278
- message:nil
279
- preferredStyle:UIAlertControllerStyleActionSheet];
421
+ - (void)handleCustomMenuAction:(UIMenuItem *)sender
422
+ {
423
+ NSLog(@"[AdvancedTextView] Custom menu action: %@", sender.title);
280
424
 
281
- for (NSString *option in _menuOptions) {
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
- UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
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
- [rootVC presentViewController:alert animated:YES completion:nil];
305
- }
429
+ dispatch_async(dispatch_get_main_queue(), ^{
430
+ self->_textView.selectedTextRange = nil;
431
+ NSLog(@"[AdvancedTextView] Selection cleared");
432
+ });
306
433
  }
307
434
 
308
- - (UIViewController *)findViewController
435
+ - (NSInteger)wordIndexAtPoint:(CGPoint)point
309
436
  {
310
- UIResponder *responder = self;
311
- while (responder) {
312
- if ([responder isKindOfClass:[UIViewController class]]) {
313
- return (UIViewController *)responder;
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
- responder = [responder nextResponder];
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
- if (_eventEmitter) {
323
- auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
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
- AdvancedTextViewEventEmitter::OnWordPress event;
326
- event.word = [word UTF8String];
327
- event.index = static_cast<int>(index);
474
+ AdvancedTextViewEventEmitter::OnWordPress event;
475
+ event.word = [word UTF8String];
476
+ event.index = static_cast<int>(index);
328
477
 
329
- emitter->onWordPress(event);
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
- if (_eventEmitter) {
336
- auto emitter = std::static_pointer_cast<const AdvancedTextViewEventEmitter>(_eventEmitter);
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
- AdvancedTextViewEventEmitter::OnSelection event;
339
- event.selectedText = [selectedText UTF8String];
340
- event.event = [option UTF8String];
492
+ AdvancedTextViewEventEmitter::OnSelection event;
493
+ event.selectedText = [selectedText UTF8String];
494
+ event.event = [option UTF8String];
341
495
 
342
- emitter->onSelection(event);
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
- [super layoutSubviews];
349
- _textView.frame = self.bounds;
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
- if (!stringToConvert || [stringToConvert length] == 0) return nil;
515
+ @try {
516
+ if (!stringToConvert || [stringToConvert length] == 0) {
517
+ return nil;
518
+ }
355
519
 
356
- NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
357
- NSScanner *stringScanner = [NSScanner scannerWithString:noHashString];
520
+ NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
521
+ NSScanner *stringScanner = [NSScanner scannerWithString:noHashString];
358
522
 
359
- unsigned hex;
360
- if (![stringScanner scanHexInt:&hex]) return nil;
523
+ unsigned hex;
524
+ if (![stringScanner scanHexInt:&hex]) {
525
+ return nil;
526
+ }
361
527
 
362
- int r = (hex >> 16) & 0xFF;
363
- int g = (hex >> 8) & 0xFF;
364
- int b = (hex) & 0xFF;
528
+ int r = (hex >> 16) & 0xFF;
529
+ int g = (hex >> 8) & 0xFF;
530
+ int b = (hex) & 0xFF;
365
531
 
366
- return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-advanced-text",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": " Advanced text component for React Native with custom select options.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",