Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 48 additions & 21 deletions Libraries/Text/Text/RCTTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import <React/RCTFocusChangeEvent.h> // [macOS]

#import <React/RCTTextShadowView.h>
#import <React/RCTTouchHandler.h>

#import <QuartzCore/QuartzCore.h>

Expand Down Expand Up @@ -208,7 +209,6 @@ - (void)drawRect:(CGRect)rect
return;
}


NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;

Expand Down Expand Up @@ -407,36 +407,65 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
}
#else // [macOS

- (NSView *)hitTest:(NSPoint)point
{
// 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).
NSView *hitView = [super hitTest:point];

NSEventType eventType = NSApp.currentEvent.type;
BOOL isMouseClickEvent = NSEvent.pressedMouseButtons > 0;
BOOL isMouseMoveEventType = eventType == NSEventTypeMouseMoved || eventType == NSEventTypeMouseEntered || eventType == NSEventTypeMouseExited || eventType == NSEventTypeCursorUpdate;
BOOL isMouseMoveEvent = !isMouseClickEvent && isMouseMoveEventType;
BOOL isTextViewClick = (hitView && hitView == _textView) && !isMouseMoveEvent;

return isTextViewClick ? self : hitView;
}

- (void)rightMouseDown:(NSEvent *)event
{
if (_selectable == NO) {

if (self.selectable == NO) {
[super rightMouseDown:event];
return;
}
NSText *fieldEditor = [self.window fieldEditor:YES forObject:self];
NSMenu *fieldEditorMenu = [fieldEditor menuForEvent:event];

RCTAssert(fieldEditorMenu, @"Unable to obtain fieldEditor's context menu");

if (fieldEditorMenu) {
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
[[RCTTouchHandler touchHandlerForView:self] cancelTouchWithEvent:event];
[_textView rightMouseDown:event];
}

for (NSMenuItem *fieldEditorMenuItem in fieldEditorMenu.itemArray) {
if (fieldEditorMenuItem.action == @selector(copy:)) {
NSMenuItem *item = [fieldEditorMenuItem copy];
- (void)mouseDown:(NSEvent *)event
{
if (!self.selectable) {
[super mouseDown:event];
return;
}

item.target = self;
[menu addItem:item];
// Double/triple-clicks should be forwarded to the NSTextView.
BOOL shouldForward = event.clickCount > 1;

break;
}
}
if (!shouldForward) {
// Peek at next event to know if a selection should begin.
NSEvent *nextEvent = [self.window nextEventMatchingMask:NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:NO];
shouldForward = nextEvent.type == NSEventTypeLeftMouseDragged;
}

RCTAssert(menu.numberOfItems > 0, @"Unable to create context menu with \"Copy\" item");
if (shouldForward) {
NSView *contentView = self.window.contentView;
// -[NSView hitTest:] takes coordinates in a view's superview coordinate system.
NSPoint point = [contentView.superview convertPoint:event.locationInWindow fromView:nil];

if (menu.numberOfItems > 0) {
[NSMenu popUpContextMenu:menu withEvent:event forView:self];
// Start selection if we're still selectable and hit-testable.
if (self.selectable && [contentView hitTest:point] == self) {
[[RCTTouchHandler touchHandlerForView:self] cancelTouchWithEvent:event];
[self.window makeFirstResponder:_textView];
[_textView mouseDown:event];
}
} else {
// Clear selection for single clicks.
_textView.selectedRange = NSMakeRange(NSNotFound, 0);
}
}
#endif // macOS]
Expand Down Expand Up @@ -533,8 +562,6 @@ - (void)copy:(id)sender
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = @[item];
#else // [macOS
[_textView copy:sender];

NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
[pasteboard setData:rtf forType:NSPasteboardTypeRTFD];
Expand Down
13 changes: 11 additions & 2 deletions Libraries/Text/TextInput/Singleline/RCTUITextField.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#import <React/RCTBackedTextInputDelegateAdapter.h>
#import <React/RCTBackedTextInputDelegate.h> // [macOS]
#import <React/RCTTextAttributes.h>

#import <React/RCTTouchHandler.h> // [TODO(macOS GH#774)

#if TARGET_OS_OSX // [macOS

Expand Down Expand Up @@ -436,7 +436,7 @@ - (CGRect)editingRectForBounds:(CGRect)bounds

#else // [macOS

#pragma mark - NSTextViewDelegate methods
#pragma mark - NSTextFieldDelegate methods

- (void)textDidChange:(NSNotification *)notification
{
Expand Down Expand Up @@ -473,6 +473,15 @@ - (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)aRange
return NO;
}

- (NSMenu *)textView:(NSTextView *)view menu:(NSMenu *)menu forEvent:(NSEvent *)event atIndex:(NSUInteger)charIndex
{
if (menu) {
[[RCTTouchHandler touchHandlerForView:self] willShowMenuWithEvent:event];
}

return menu;
}

#endif // macOS]

#pragma mark - Overrides
Expand Down
7 changes: 6 additions & 1 deletion React/Base/RCTTouchHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@
- (void)detachFromView:(RCTUIView *)view; // [macOS]

- (void)cancel;

#if TARGET_OS_OSX // [macOS
- (void)willShowMenuWithEvent:(NSEvent*)event;
+ (instancetype)touchHandlerForEvent:(NSEvent *)event;
+ (instancetype)touchHandlerForView:(NSView *)view;

- (void)willShowMenuWithEvent:(NSEvent *)event;
- (void)cancelTouchWithEvent:(NSEvent *)event;
#endif // macOS]

@end
32 changes: 31 additions & 1 deletion React/Base/RCTTouchHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -577,12 +577,42 @@ - (void)cancel
}

#if TARGET_OS_OSX // [macOS
- (void)willShowMenuWithEvent:(NSEvent*)event
+ (instancetype)touchHandlerForEvent:(NSEvent *)event {
// The window's frame view must be used for hit testing against `locationInWindow`
NSView *hitView = [event.window.contentView.superview hitTest:event.locationInWindow];
return [self touchHandlerForView:hitView];
}

+ (instancetype)touchHandlerForView:(NSView *)view {
if ([view isKindOfClass:[RCTRootView class]]) {
// The RCTTouchHandler is attached to the contentView.
view = ((RCTRootView *)view).contentView;
}

while (view) {
for (NSGestureRecognizer *gestureRecognizer in view.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[self class]]) {
return (RCTTouchHandler *)gestureRecognizer;
}
}

view = view.superview;
}

return nil;
}

- (void)willShowMenuWithEvent:(NSEvent *)event
{
if (event.type == NSEventTypeRightMouseDown) {
[self interactionsEnded:[NSSet setWithObject:event] withEvent:event];
}
}

- (void)cancelTouchWithEvent:(NSEvent *)event
{
[self interactionsCancelled:[NSSet setWithObject:event] withEvent:event];
}
#endif // macOS]

#pragma mark - UIGestureRecognizerDelegate
Expand Down