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