Skip to content

Only forward click events to NSTextview #1515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
70 changes: 49 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> // TODO(OSS Candidate ISS#2710739)

#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,38 +407,68 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
}
#else // [TODO(macOS GH#774)

- (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 // ]TODO(macOS GH#774)

#pragma mark - Selection
Expand Down Expand Up @@ -533,8 +563,6 @@ - (void)copy:(id)sender
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = @[item];
#elif TARGET_OS_OSX
[_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> // TODO(OSS Candidate ISS#2710739)
#import <React/RCTTextAttributes.h>

#import <React/RCTTouchHandler.h>

#if TARGET_OS_OSX // [TODO(macOS GH#774)

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

#else // [TODO(macOS GH#774)

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

- (void)textDidChange:(NSNotification *)notification
{
Expand Down Expand Up @@ -467,6 +467,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 // ]TODO(macOS GH#774)

#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; // TODO(macOS ISS#3536887)

- (void)cancel;

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

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

@end
34 changes: 33 additions & 1 deletion React/Base/RCTTouchHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -576,12 +576,44 @@ - (void)cancel
}

#if TARGET_OS_OSX // [TODO(macOS GH#774)
- (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 // ]TODO(macOS GH#774)

#pragma mark - UIGestureRecognizerDelegate
Expand Down