@thiagobueno/rn-selectable-text 1.0.7 → 1.0.8
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/SelectableTextView.mm +34 -28
- package/package.json +1 -1
|
@@ -239,42 +239,26 @@ static std::string safeStdString(NSString *str) {
|
|
|
239
239
|
#pragma mark - UITextViewDelegate
|
|
240
240
|
|
|
241
241
|
// ====================================================================
|
|
242
|
-
//
|
|
242
|
+
// TOUCH-UP DETECTION & DEBOUNCE LOGIC
|
|
243
243
|
// ====================================================================
|
|
244
244
|
- (void)textViewDidChangeSelection:(UITextView *)textView
|
|
245
245
|
{
|
|
246
|
+
// CANCEL PREVIOUS TIMERS: If user is still dragging, reset the clock
|
|
247
|
+
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(emitCustomModeEventOrShowMenu) object:nil];
|
|
248
|
+
|
|
246
249
|
if (textView.selectedRange.location != NSNotFound && textView.selectedRange.length > 0) {
|
|
247
250
|
|
|
248
|
-
// SAFE BOUNDS CHECK: Prevents iOS 15/17/18 out-of-bounds crash
|
|
249
|
-
NSString *selectedText = @"";
|
|
250
|
-
if (textView.selectedRange.location + textView.selectedRange.length <= textView.text.length) {
|
|
251
|
-
selectedText = [textView.text substringWithRange:textView.selectedRange];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// INVISIBLE MODE (ALL IOS VERSIONS)
|
|
255
|
-
if (_menuOptions.count == 0) {
|
|
256
|
-
if (auto eventEmitter = std::static_pointer_cast<const SelectableTextViewEventEmitter>(_eventEmitter)) {
|
|
257
|
-
SelectableTextViewEventEmitter::OnSelection selectionEvent = {
|
|
258
|
-
.chosenOption = "CUSTOM_MODE",
|
|
259
|
-
.highlightedText = safeStdString(selectedText)
|
|
260
|
-
};
|
|
261
|
-
eventEmitter->onSelection(selectionEvent);
|
|
262
|
-
}
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// STANDARD MODE
|
|
267
251
|
if (@available(iOS 16.0, *)) {
|
|
252
|
+
// iOS 16+: Do nothing during drag. The event will fire perfectly on 'Touch Up' inside editMenuForTextInRange.
|
|
268
253
|
return;
|
|
269
254
|
} else {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
});
|
|
255
|
+
// iOS 15: Debounce timer. Waits 0.4s after the user stops dragging before firing the event.
|
|
256
|
+
[self performSelector:@selector(emitCustomModeEventOrShowMenu) withObject:nil afterDelay:0.4];
|
|
273
257
|
}
|
|
274
258
|
} else {
|
|
275
259
|
[[UIMenuController sharedMenuController] hideMenuFromView:_customTextView];
|
|
276
260
|
|
|
277
|
-
// IF SELECTION IS CLEARED, NOTIFY JS
|
|
261
|
+
// IF SELECTION IS CLEARED, NOTIFY JS IMMEDIATELY
|
|
278
262
|
if (_menuOptions.count == 0) {
|
|
279
263
|
if (auto eventEmitter = std::static_pointer_cast<const SelectableTextViewEventEmitter>(_eventEmitter)) {
|
|
280
264
|
SelectableTextViewEventEmitter::OnSelection selectionEvent = {
|
|
@@ -287,8 +271,33 @@ static std::string safeStdString(NSString *str) {
|
|
|
287
271
|
}
|
|
288
272
|
}
|
|
289
273
|
|
|
274
|
+
- (void)emitCustomModeEventOrShowMenu {
|
|
275
|
+
if (_customTextView.selectedRange.location == NSNotFound || _customTextView.selectedRange.length == 0) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (_menuOptions.count == 0) {
|
|
280
|
+
// INVISIBLE MODE: Emit event on touch up
|
|
281
|
+
NSString *selectedText = @"";
|
|
282
|
+
if (_customTextView.selectedRange.location + _customTextView.selectedRange.length <= _customTextView.text.length) {
|
|
283
|
+
selectedText = [_customTextView.text substringWithRange:_customTextView.selectedRange];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (auto eventEmitter = std::static_pointer_cast<const SelectableTextViewEventEmitter>(_eventEmitter)) {
|
|
287
|
+
SelectableTextViewEventEmitter::OnSelection selectionEvent = {
|
|
288
|
+
.chosenOption = "CUSTOM_MODE",
|
|
289
|
+
.highlightedText = safeStdString(selectedText)
|
|
290
|
+
};
|
|
291
|
+
eventEmitter->onSelection(selectionEvent);
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
// NATIVE MODE: Show the old UIMenuController
|
|
295
|
+
[self showCustomMenu];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
290
299
|
// ====================================================================
|
|
291
|
-
// THE NEW IOS 16+ API
|
|
300
|
+
// THE NEW IOS 16+ API (FIRES ONLY ON TOUCH UP)
|
|
292
301
|
// ====================================================================
|
|
293
302
|
- (UIMenu *)textView:(UITextView *)textView editMenuForTextInRange:(NSRange)range suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0)) {
|
|
294
303
|
|
|
@@ -349,7 +358,6 @@ static std::string safeStdString(NSString *str) {
|
|
|
349
358
|
|
|
350
359
|
CGRect selectedRect = [_customTextView firstRectForRange:_customTextView.selectedTextRange];
|
|
351
360
|
|
|
352
|
-
// CRASH PREVENTION: Validate rect before showing menu (iOS 15 safety)
|
|
353
361
|
if (!CGRectIsEmpty(selectedRect) && !CGRectIsNull(selectedRect) && !CGRectIsInfinite(selectedRect)) {
|
|
354
362
|
CGRect targetRect = [_customTextView convertRect:selectedRect toView:_customTextView];
|
|
355
363
|
[menuController showMenuFromView:_customTextView rect:targetRect];
|
|
@@ -396,7 +404,6 @@ static std::string safeStdString(NSString *str) {
|
|
|
396
404
|
NSString *selectorName = NSStringFromSelector(anInvocation.selector);
|
|
397
405
|
|
|
398
406
|
if ([selectorName hasPrefix:@"customAction_"] && [selectorName hasSuffix:@":"]) {
|
|
399
|
-
// CRASH PREVENTION: Validate selector length before cutting string
|
|
400
407
|
if (selectorName.length > 14) {
|
|
401
408
|
NSString *cleanedOption = [selectorName substringWithRange:NSMakeRange(13, selectorName.length - 14)];
|
|
402
409
|
|
|
@@ -426,7 +433,6 @@ static std::string safeStdString(NSString *str) {
|
|
|
426
433
|
NSRange selectedRange = _customTextView.selectedRange;
|
|
427
434
|
NSString *selectedText = @"";
|
|
428
435
|
|
|
429
|
-
// SAFE BOUNDS CHECK
|
|
430
436
|
if (selectedRange.location != NSNotFound && selectedRange.length > 0 && (selectedRange.location + selectedRange.length <= _customTextView.text.length)) {
|
|
431
437
|
selectedText = [_customTextView.text substringWithRange:selectedRange];
|
|
432
438
|
}
|