Skip to content

Commit a2b3c15

Browse files
committed
feat: add RCTWindow
By subclassing NSWindow and overriding the "sendEvent:" method, we can avoid using the NSGestureRecognizer class, which seems to cause issues with NSTextView event handling. The RCTWindow class reimplements input event handling, which solves the following issues: - Skip "touchStart" events that target a focused NSTextView - Emit "mouseOut" event when the mouse leaves the RCTWindow - Emit "touchCancel" when the mouse leaves the RCTWindow - Support "mouseMove" events (which can be coalesced) - Emit "mouseMove" event right after "mouseUp" events - Blur the focused NSTextView when clicking outside it - Support "contextMenu" events - Add "altKey", "ctrlKey", "metaKey", and "shiftKey" properties to JS mouse events - Use "convertPoint:toView:" to compute the relative mouse location
1 parent 7af91f8 commit a2b3c15

9 files changed

Lines changed: 453 additions & 2 deletions

File tree

React/Base/RCTMouseEvent.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <Foundation/Foundation.h>
11+
12+
#import <React/RCTEventDispatcher.h>
13+
14+
@interface RCTMouseEvent : NSObject <RCTEvent>
15+
16+
- (instancetype)initWithEventName:(NSString *)eventName
17+
target:(NSNumber *)target
18+
userInfo:(NSDictionary *)userInfo
19+
coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER;
20+
21+
@property (readonly) NSTimeInterval timestamp;
22+
23+
@end

React/Base/RCTMouseEvent.m

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTMouseEvent.h"
11+
12+
#import "RCTAssert.h"
13+
14+
@implementation RCTMouseEvent
15+
{
16+
NSDictionary *_userInfo;
17+
uint16_t _coalescingKey;
18+
}
19+
20+
@synthesize eventName = _eventName;
21+
@synthesize viewTag = _viewTag;
22+
23+
- (instancetype)initWithEventName:(NSString *)eventName
24+
target:(NSNumber *)target
25+
userInfo:(NSDictionary *)userInfo
26+
coalescingKey:(uint16_t)coalescingKey
27+
{
28+
if (self = [super init]) {
29+
_viewTag = target;
30+
_userInfo = userInfo;
31+
_eventName = eventName;
32+
_coalescingKey = coalescingKey;
33+
}
34+
return self;
35+
}
36+
37+
RCT_NOT_IMPLEMENTED(- (instancetype)init)
38+
39+
#pragma mark - RCTEvent
40+
41+
- (BOOL)canCoalesce
42+
{
43+
return [_eventName isEqual:@"mouseMove"];
44+
}
45+
46+
// We coalesce only move events, while holding some assumptions that seem reasonable but there are no explicit guarantees about them.
47+
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
48+
{
49+
RCTAssert([newEvent isKindOfClass:[RCTMouseEvent class]], @"Mouse event cannot be coalesced with any other type of event, such as provided %@", newEvent);
50+
return ((RCTMouseEvent *)newEvent).timestamp > self.timestamp ? newEvent : self;
51+
}
52+
53+
+ (NSString *)moduleDotMethod
54+
{
55+
return @"RCTEventEmitter.receiveEvent";
56+
}
57+
58+
- (NSArray *)arguments
59+
{
60+
return @[_viewTag, RCTNormalizeInputEventName(_eventName), _userInfo];
61+
}
62+
63+
- (uint16_t)coalescingKey
64+
{
65+
return _coalescingKey;
66+
}
67+
68+
- (NSString *)description
69+
{
70+
return [NSString stringWithFormat:@"<%@: %p; name = %@; coalescing key = %hu>", [self class], self, _eventName, _coalescingKey];
71+
}
72+
73+
- (NSTimeInterval)timestamp
74+
{
75+
return [_userInfo[@"timestamp"] doubleValue];
76+
}
77+
78+
@end

React/Base/RCTRootContentView.m

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#import "RCTRootViewInternal.h"
1616
#import "RCTTouchHandler.h"
1717
#import "RCTUIManager.h"
18+
#import "RCTWindow.h"
1819
#import "NSView+React.h"
1920

2021
@implementation RCTRootContentView
@@ -28,8 +29,6 @@ - (instancetype)initWithFrame:(CGRect)frame
2829
_bridge = bridge;
2930
self.reactTag = reactTag;
3031
_sizeFlexibility = sizeFlexibility;
31-
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
32-
[_touchHandler attachToView:self];
3332
[_bridge.uiManager registerRootView:self];
3433
}
3534
return self;
@@ -108,4 +107,21 @@ - (void)invalidate
108107
//}
109108
}
110109

110+
- (void)viewDidMoveToWindow
111+
{
112+
if (self.window == nil) {
113+
return;
114+
}
115+
// RCTWindow handles all touches within
116+
if ([self.window isKindOfClass:RCTWindow.class] == NO) {
117+
if (_touchHandler == nil) {
118+
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
119+
[_touchHandler attachToView:self];
120+
}
121+
} else if (_touchHandler) {
122+
[_touchHandler detachFromView:self];
123+
_touchHandler = nil;
124+
}
125+
}
126+
111127
@end

React/React.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,12 @@
10381038
66CD94B71F1045E700CB3C7C /* RCTMaskedViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 66CD94B01F1045E700CB3C7C /* RCTMaskedViewManager.m */; };
10391039
66CD94B81F1045E700CB3C7C /* RCTMaskedViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 66CD94B01F1045E700CB3C7C /* RCTMaskedViewManager.m */; };
10401040
68EFE4EE1CF6EB3900A1DE13 /* RCTBundleURLProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */; };
1041+
702B7FF8221C88AF0027174A /* RCTWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 702B7FF6221C88AF0027174A /* RCTWindow.h */; };
1042+
702B7FF9221C88AF0027174A /* RCTWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 702B7FF7221C88AF0027174A /* RCTWindow.m */; };
1043+
702B7FFC221C88BB0027174A /* RCTMouseEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 702B7FFA221C88BB0027174A /* RCTMouseEvent.h */; };
1044+
702B7FFD221C88BB0027174A /* RCTMouseEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 702B7FFB221C88BB0027174A /* RCTMouseEvent.m */; };
1045+
702B7FFE221C88CE0027174A /* RCTMouseEvent.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 702B7FFA221C88BB0027174A /* RCTMouseEvent.h */; };
1046+
702B7FFF221C88D70027174A /* RCTWindow.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 702B7FF6221C88AF0027174A /* RCTWindow.h */; };
10411047
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
10421048
83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */; };
10431049
83A1FE8C1B62640A00BE0E65 /* RCTModalHostView.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A1FE8B1B62640A00BE0E65 /* RCTModalHostView.m */; };
@@ -1469,6 +1475,8 @@
14691475
dstPath = include/React;
14701476
dstSubfolderSpec = 16;
14711477
files = (
1478+
702B7FFF221C88D70027174A /* RCTWindow.h in Copy Headers */,
1479+
702B7FFE221C88CE0027174A /* RCTMouseEvent.h in Copy Headers */,
14721480
D4EEE3542020933B00C4CBB6 /* UIImageUtils.h in Copy Headers */,
14731481
59EDBCBD1FDF4E43003573DE /* RCTScrollableProtocol.h in Copy Headers */,
14741482
59EDBCBE1FDF4E43003573DE /* RCTScrollContentShadowView.h in Copy Headers */,
@@ -2064,6 +2072,10 @@
20642072
68EFE4EC1CF6EB3000A1DE13 /* RCTBundleURLProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBundleURLProvider.h; sourceTree = "<group>"; };
20652073
68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBundleURLProvider.m; sourceTree = "<group>"; };
20662074
6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootViewInternal.h; sourceTree = "<group>"; };
2075+
702B7FF6221C88AF0027174A /* RCTWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWindow.h; sourceTree = "<group>"; };
2076+
702B7FF7221C88AF0027174A /* RCTWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWindow.m; sourceTree = "<group>"; };
2077+
702B7FFA221C88BB0027174A /* RCTMouseEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMouseEvent.h; sourceTree = "<group>"; };
2078+
702B7FFB221C88BB0027174A /* RCTMouseEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMouseEvent.m; sourceTree = "<group>"; };
20672079
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
20682080
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
20692081
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
@@ -2445,6 +2457,8 @@
24452457
13C156021AB1A2840079392D /* RCTWebView.m */,
24462458
13C156031AB1A2840079392D /* RCTWebViewManager.h */,
24472459
13C156041AB1A2840079392D /* RCTWebViewManager.m */,
2460+
702B7FF6221C88AF0027174A /* RCTWindow.h */,
2461+
702B7FF7221C88AF0027174A /* RCTWindow.m */,
24482462
59D031E41F8353D3008361F0 /* SafeAreaView */,
24492463
59EDBC9B1FDF4E0C003573DE /* ScrollView */,
24502464
83F15A171B7CC46900F10295 /* NSView+Private.h */,
@@ -2768,6 +2782,8 @@
27682782
14C2CA731B3AC64300E6CBB2 /* RCTModuleData.mm */,
27692783
14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
27702784
C606692D1F3CC60500E67165 /* RCTModuleMethod.mm */,
2785+
702B7FFA221C88BB0027174A /* RCTMouseEvent.h */,
2786+
702B7FFB221C88BB0027174A /* RCTMouseEvent.m */,
27712787
006FC4121D9B20820057AAAD /* RCTMultipartDataTask.h */,
27722788
006FC4131D9B20820057AAAD /* RCTMultipartDataTask.m */,
27732789
001BFCCE1D8381DE008E587E /* RCTMultipartStreamReader.h */,
@@ -3212,6 +3228,7 @@
32123228
3D80DA221DF820620028D040 /* RCTBridge+Private.h in Headers */,
32133229
599FAA461FB274980058CCF6 /* RCTSurfaceStage.h in Headers */,
32143230
599FAA361FB274980058CCF6 /* RCTSurface.h in Headers */,
3231+
702B7FFC221C88BB0027174A /* RCTMouseEvent.h in Headers */,
32153232
3D80DA231DF820620028D040 /* RCTBridgeDelegate.h in Headers */,
32163233
3D80DA241DF820620028D040 /* RCTBridgeMethod.h in Headers */,
32173234
3D7BFD151EA8E351008DFB7A /* RCTPackagerClient.h in Headers */,
@@ -3323,6 +3340,7 @@
33233340
3DF1BE831F26576400068F1A /* JSCTracing.h in Headers */,
33243341
3D80DA721DF820620028D040 /* RCTModalHostViewManager.h in Headers */,
33253342
13134C9C1E296B2A00B9F3CB /* RCTCxxModule.h in Headers */,
3343+
702B7FF8221C88AF0027174A /* RCTWindow.h in Headers */,
33263344
594F0A321FD23228007FBE96 /* RCTSurfaceHostingView.h in Headers */,
33273345
3D80DA771DF820620028D040 /* RCTPicker.h in Headers */,
33283346
3D80DA781DF820620028D040 /* RCTPickerManager.h in Headers */,
@@ -4142,6 +4160,7 @@
41424160
001BFCD01D8381DE008E587E /* RCTMultipartStreamReader.m in Sources */,
41434161
133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */,
41444162
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
4163+
702B7FF9221C88AF0027174A /* RCTWindow.m in Sources */,
41454164
D49593E6202C96FF00A7694B /* YGNode.cpp in Sources */,
41464165
594F0A341FD23228007FBE96 /* RCTSurfaceHostingView.mm in Sources */,
41474166
13134C861E296B2A00B9F3CB /* RCTCxxBridge.mm in Sources */,
@@ -4255,6 +4274,7 @@
42554274
135A9BFC1E7B0EAE00587AEB /* RCTJSCErrorHandling.mm in Sources */,
42564275
83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */,
42574276
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
4277+
702B7FFD221C88BB0027174A /* RCTMouseEvent.m in Sources */,
42584278
83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */,
42594279
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
42604280
1384E2091E806D4E00545659 /* RCTNativeModule.mm in Sources */,

React/Views/NSView+React.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,6 @@
114114
*/
115115
@property (nonatomic, assign) BOOL clipsToBounds;
116116

117+
- (NSView *)reactHitTest:(NSPoint)point;
118+
117119
@end

React/Views/NSView+React.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,15 @@ - (NSView *)reactAccessibilityElement
295295
return self;
296296
}
297297

298+
#pragma mark - Interaction
299+
300+
- (NSView *)reactHitTest:(NSPoint)point
301+
{
302+
NSView *view = [self hitTest:point];
303+
while (view && !view.reactTag) {
304+
view = view.superview;
305+
}
306+
return view;
307+
}
308+
298309
@end

React/Views/RCTViewManager.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ - (RCTShadowView *)shadowView
7373
@"touchEnd",
7474

7575
// Mouse events
76+
@"mouseMove",
7677
@"mouseOver",
7778
@"mouseOut",
79+
@"contextMenu",
7880
];
7981
}
8082

React/Views/RCTWindow.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <AppKit/AppKit.h>
11+
12+
#import "RCTBridge.h"
13+
#import "RCTRootView.h"
14+
15+
@interface RCTWindow : NSWindow
16+
17+
- (instancetype)initWithBridge:(RCTBridge *)bridge
18+
contentRect:(NSRect)contentRect
19+
styleMask:(NSWindowStyleMask)style
20+
defer:(BOOL)defer NS_DESIGNATED_INITIALIZER;
21+
22+
@property (nullable, strong) RCTRootView *contentView;
23+
24+
@end

0 commit comments

Comments
 (0)