|
17 | 17 | #import <React/RCTFocusChangeEvent.h> // [macOS]
|
18 | 18 |
|
19 | 19 | #import <React/RCTTextShadowView.h>
|
| 20 | +#import <React/RCTTouchHandler.h> |
20 | 21 |
|
21 | 22 | #import <QuartzCore/QuartzCore.h>
|
22 | 23 |
|
@@ -208,7 +209,6 @@ - (void)drawRect:(CGRect)rect
|
208 | 209 | return;
|
209 | 210 | }
|
210 | 211 |
|
211 |
| - |
212 | 212 | NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject;
|
213 | 213 | NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
|
214 | 214 |
|
@@ -407,36 +407,65 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
|
407 | 407 | }
|
408 | 408 | #else // [macOS
|
409 | 409 |
|
| 410 | +- (NSView *)hitTest:(NSPoint)point |
| 411 | +{ |
| 412 | + // We will forward mouse click events to the NSTextView ourselves to prevent NSTextView from swallowing events that may be handled in JS (e.g. long press). |
| 413 | + NSView *hitView = [super hitTest:point]; |
| 414 | + |
| 415 | + NSEventType eventType = NSApp.currentEvent.type; |
| 416 | + BOOL isMouseClickEvent = NSEvent.pressedMouseButtons > 0; |
| 417 | + BOOL isMouseMoveEventType = eventType == NSEventTypeMouseMoved || eventType == NSEventTypeMouseEntered || eventType == NSEventTypeMouseExited || eventType == NSEventTypeCursorUpdate; |
| 418 | + BOOL isMouseMoveEvent = !isMouseClickEvent && isMouseMoveEventType; |
| 419 | + BOOL isTextViewClick = (hitView && hitView == _textView) && !isMouseMoveEvent; |
| 420 | + |
| 421 | + return isTextViewClick ? self : hitView; |
| 422 | +} |
| 423 | + |
410 | 424 | - (void)rightMouseDown:(NSEvent *)event
|
411 | 425 | {
|
412 |
| - if (_selectable == NO) { |
| 426 | + |
| 427 | + if (self.selectable == NO) { |
413 | 428 | [super rightMouseDown:event];
|
414 | 429 | return;
|
415 | 430 | }
|
416 |
| - NSText *fieldEditor = [self.window fieldEditor:YES forObject:self]; |
417 |
| - NSMenu *fieldEditorMenu = [fieldEditor menuForEvent:event]; |
418 | 431 |
|
419 |
| - RCTAssert(fieldEditorMenu, @"Unable to obtain fieldEditor's context menu"); |
420 |
| - |
421 |
| - if (fieldEditorMenu) { |
422 |
| - NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; |
| 432 | + [[RCTTouchHandler touchHandlerForView:self] cancelTouchWithEvent:event]; |
| 433 | + [_textView rightMouseDown:event]; |
| 434 | +} |
423 | 435 |
|
424 |
| - for (NSMenuItem *fieldEditorMenuItem in fieldEditorMenu.itemArray) { |
425 |
| - if (fieldEditorMenuItem.action == @selector(copy:)) { |
426 |
| - NSMenuItem *item = [fieldEditorMenuItem copy]; |
| 436 | +- (void)mouseDown:(NSEvent *)event |
| 437 | +{ |
| 438 | + if (!self.selectable) { |
| 439 | + [super mouseDown:event]; |
| 440 | + return; |
| 441 | + } |
427 | 442 |
|
428 |
| - item.target = self; |
429 |
| - [menu addItem:item]; |
| 443 | + // Double/triple-clicks should be forwarded to the NSTextView. |
| 444 | + BOOL shouldForward = event.clickCount > 1; |
430 | 445 |
|
431 |
| - break; |
432 |
| - } |
433 |
| - } |
| 446 | + if (!shouldForward) { |
| 447 | + // Peek at next event to know if a selection should begin. |
| 448 | + NSEvent *nextEvent = [self.window nextEventMatchingMask:NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged |
| 449 | + untilDate:[NSDate distantFuture] |
| 450 | + inMode:NSEventTrackingRunLoopMode |
| 451 | + dequeue:NO]; |
| 452 | + shouldForward = nextEvent.type == NSEventTypeLeftMouseDragged; |
| 453 | + } |
434 | 454 |
|
435 |
| - RCTAssert(menu.numberOfItems > 0, @"Unable to create context menu with \"Copy\" item"); |
| 455 | + if (shouldForward) { |
| 456 | + NSView *contentView = self.window.contentView; |
| 457 | + // -[NSView hitTest:] takes coordinates in a view's superview coordinate system. |
| 458 | + NSPoint point = [contentView.superview convertPoint:event.locationInWindow fromView:nil]; |
436 | 459 |
|
437 |
| - if (menu.numberOfItems > 0) { |
438 |
| - [NSMenu popUpContextMenu:menu withEvent:event forView:self]; |
| 460 | + // Start selection if we're still selectable and hit-testable. |
| 461 | + if (self.selectable && [contentView hitTest:point] == self) { |
| 462 | + [[RCTTouchHandler touchHandlerForView:self] cancelTouchWithEvent:event]; |
| 463 | + [self.window makeFirstResponder:_textView]; |
| 464 | + [_textView mouseDown:event]; |
439 | 465 | }
|
| 466 | + } else { |
| 467 | + // Clear selection for single clicks. |
| 468 | + _textView.selectedRange = NSMakeRange(NSNotFound, 0); |
440 | 469 | }
|
441 | 470 | }
|
442 | 471 | #endif // macOS]
|
@@ -533,8 +562,6 @@ - (void)copy:(id)sender
|
533 | 562 | UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
534 | 563 | pasteboard.items = @[item];
|
535 | 564 | #else // [macOS
|
536 |
| - [_textView copy:sender]; |
537 |
| - |
538 | 565 | NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
539 | 566 | [pasteboard clearContents];
|
540 | 567 | [pasteboard setData:rtf forType:NSPasteboardTypeRTFD];
|
|
0 commit comments