@thiagobueno/rn-selectable-text 1.0.5 → 1.0.6

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.
@@ -20,8 +20,6 @@ using namespace facebook::react;
20
20
 
21
21
  - (void)didMoveToWindow {
22
22
  [super didMoveToWindow];
23
- // FIX: iOS 16+ text selection bug after navigation.
24
- // When returning to the screen, we force iOS to re-evaluate the interaction state.
25
23
  if (self.window) {
26
24
  BOOL wasSelectable = self.selectable;
27
25
  self.selectable = NO;
@@ -31,12 +29,8 @@ using namespace facebook::react;
31
29
 
32
30
  - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
33
31
  {
34
- // FIX: We explicitly tell iOS that we can perform standard actions.
35
- // This prevents the "[UIKitCore] The edit menu did not have performable commands" warning.
36
- if (action == @selector(copy:) || action == @selector(selectAll:)) {
37
- return YES;
38
- }
39
-
32
+ // FIX: Remove hardcoded YES. We delegate entirely to the parent logic
33
+ // to allow complete suppression of the menu in Custom Mode.
40
34
  if (self.parentSelectableTextView) {
41
35
  return [self.parentSelectableTextView canPerformAction:action withSender:sender];
42
36
  }
@@ -63,7 +57,6 @@ using namespace facebook::react;
63
57
  }
64
58
  }
65
59
 
66
- // Intercepts physical keyboard "Copy" and nullifies the action to keep it custom
67
60
  - (void)copy:(id)sender
68
61
  {
69
62
  // Silently blocked
@@ -142,7 +135,6 @@ using namespace facebook::react;
142
135
  - (void)prepareForRecycle {
143
136
  [super prepareForRecycle];
144
137
 
145
- // FIX: Force drop focus when leaving screen
146
138
  [_customTextView resignFirstResponder];
147
139
 
148
140
  [[UIMenuController sharedMenuController] hideMenuFromView:_customTextView];
@@ -151,7 +143,6 @@ using namespace facebook::react;
151
143
  _customTextView.text = nil;
152
144
  _customTextView.selectedTextRange = nil;
153
145
 
154
- // CRITICAL FIX: Do NOT clear _menuOptions here. Fabric's prop diffing will handle updates.
155
146
  [self unhideAllViews:self];
156
147
  }
157
148
 
@@ -241,19 +232,14 @@ using namespace facebook::react;
241
232
  #pragma mark - UITextViewDelegate
242
233
 
243
234
  // ====================================================================
244
- // THE NEW IOS 16+ API
235
+ // REAL-TIME CUSTOM MODE TRACKING
245
236
  // ====================================================================
246
- - (UIMenu *)textView:(UITextView *)textView editMenuForTextInRange:(NSRange)range suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0)) {
247
-
248
- // FORCING THE FOCUS: ensures the system doesn't dismiss our menu unexpectedly
249
- [textView becomeFirstResponder];
250
-
251
- // ====================================================================
252
- // CUSTOM INVISIBLE MODE: Suppress native menu if array is empty
253
- // ====================================================================
254
- if (_menuOptions.count == 0) {
255
- NSString *selectedText = [textView.text substringWithRange:range];
256
- if (selectedText.length > 0) {
237
+ - (void)textViewDidChangeSelection:(UITextView *)textView
238
+ {
239
+ if (textView.selectedRange.length > 0) {
240
+ // INVISIBLE MODE (ALL IOS VERSIONS)
241
+ if (_menuOptions.count == 0) {
242
+ NSString *selectedText = [textView.text substringWithRange:textView.selectedRange];
257
243
  if (auto eventEmitter = std::static_pointer_cast<const SelectableTextViewEventEmitter>(_eventEmitter)) {
258
244
  SelectableTextViewEventEmitter::OnSelection selectionEvent = {
259
245
  .chosenOption = std::string("CUSTOM_MODE"),
@@ -261,10 +247,43 @@ using namespace facebook::react;
261
247
  };
262
248
  eventEmitter->onSelection(selectionEvent);
263
249
  }
250
+ return;
264
251
  }
265
- // Returns an empty menu, effectively hiding the native UI
252
+
253
+ // STANDARD MODE
254
+ if (@available(iOS 16.0, *)) {
255
+ return;
256
+ } else {
257
+ dispatch_async(dispatch_get_main_queue(), ^{
258
+ [self showCustomMenu];
259
+ });
260
+ }
261
+ } else {
262
+ [[UIMenuController sharedMenuController] hideMenuFromView:_customTextView];
263
+
264
+ // IF SELECTION IS CLEARED, NOTIFY JS TO HIDE THE BOTTOM SHEET
265
+ if (_menuOptions.count == 0) {
266
+ if (auto eventEmitter = std::static_pointer_cast<const SelectableTextViewEventEmitter>(_eventEmitter)) {
267
+ SelectableTextViewEventEmitter::OnSelection selectionEvent = {
268
+ .chosenOption = std::string("CUSTOM_MODE"),
269
+ .highlightedText = std::string("")
270
+ };
271
+ eventEmitter->onSelection(selectionEvent);
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ // ====================================================================
278
+ // THE NEW IOS 16+ API
279
+ // ====================================================================
280
+ - (UIMenu *)textView:(UITextView *)textView editMenuForTextInRange:(NSRange)range suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0)) {
281
+
282
+ if (_menuOptions.count == 0) {
266
283
  return [UIMenu menuWithTitle:@"" children:@[]];
267
284
  }
285
+
286
+ [textView becomeFirstResponder];
268
287
 
269
288
  NSMutableArray<UIMenuElement *> *customActions = [[NSMutableArray alloc] init];
270
289
 
@@ -278,36 +297,6 @@ using namespace facebook::react;
278
297
  return [UIMenu menuWithTitle:@"" children:customActions];
279
298
  }
280
299
 
281
- - (void)textViewDidChangeSelection:(UITextView *)textView
282
- {
283
- if (@available(iOS 16.0, *)) {
284
- return;
285
- } else {
286
- if (textView.selectedRange.length > 0) {
287
-
288
- // CUSTOM INVISIBLE MODE FOR IOS < 16
289
- if (_menuOptions.count == 0) {
290
- NSString *selectedText = [textView.text substringWithRange:textView.selectedRange];
291
- if (auto eventEmitter = std::static_pointer_cast<const SelectableTextViewEventEmitter>(_eventEmitter)) {
292
- SelectableTextViewEventEmitter::OnSelection selectionEvent = {
293
- .chosenOption = std::string("CUSTOM_MODE"),
294
- .highlightedText = std::string([selectedText UTF8String])
295
- };
296
- eventEmitter->onSelection(selectionEvent);
297
- }
298
- [[UIMenuController sharedMenuController] hideMenuFromView:_customTextView];
299
- return;
300
- }
301
-
302
- dispatch_async(dispatch_get_main_queue(), ^{
303
- [self showCustomMenu];
304
- });
305
- } else {
306
- [[UIMenuController sharedMenuController] hideMenuFromView:_customTextView];
307
- }
308
- }
309
- }
310
-
311
300
  - (void)showCustomMenu
312
301
  {
313
302
  if (![_customTextView canBecomeFirstResponder]) return;
@@ -346,17 +335,20 @@ using namespace facebook::react;
346
335
  }
347
336
 
348
337
  // ====================================================================
349
- // THE TRICK TO FORCE THE MENU TO OPEN AND PREVENT UIKIT WARNINGS
338
+ // SUPPRESS SYSTEM MENU IN CUSTOM MODE
350
339
  // ====================================================================
351
340
  - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
352
341
  {
342
+ // 100% INVISIBLE MODE: iOS is forbidden to render any bubble menu
343
+ if (_menuOptions.count == 0) {
344
+ return NO;
345
+ }
346
+
353
347
  NSString *selectorName = NSStringFromSelector(action);
354
348
  if ([selectorName hasPrefix:@"customAction_"] && [selectorName hasSuffix:@":"]) {
355
349
  return YES;
356
350
  }
357
351
 
358
- // We tell iOS that we can perform standard actions.
359
- // This convinces the system not to abort the menu rendering.
360
352
  if (action == @selector(copy:) || action == @selector(selectAll:)) {
361
353
  return YES;
362
354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thiagobueno/rn-selectable-text",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "A library for custom text selection menus",
6
6
  "main": "./lib/module/index.js",