Skip to content
This repository was archived by the owner on Nov 11, 2024. It is now read-only.

Commit d679a7c

Browse files
committed
feat: add "cursor" prop to RCTView
1 parent e63b776 commit d679a7c

File tree

14 files changed

+125
-54
lines changed

14 files changed

+125
-54
lines changed

@types/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,6 +2093,11 @@ export interface ViewProps
20932093
*/
20942094
pointerEvents?: "box-none" | "none" | "box-only" | "auto";
20952095

2096+
/**
2097+
* The cursor image to show while the mouse is in inside this view.
2098+
*/
2099+
cursor?: "inherit" | "none" | "default" | "pointer" | "text" | "move" | "grab" | "grabbing"
2100+
20962101
/**
20972102
*
20982103
* This is a special performance property exposed by RCTView and is useful for scrolling content when there are many subviews,

Libraries/Components/View/ViewPropTypes.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,16 @@ module.exports = {
443443
onDrop: PropTypes.func,
444444
onContextMenu: PropTypes.func,
445445
onContextMenuItemClick: PropTypes.func,
446+
cursor: PropTypes.oneOf([
447+
'inherit',
448+
'none',
449+
'default',
450+
'pointer',
451+
'text',
452+
'move',
453+
'grab',
454+
'grabbing',
455+
]),
446456
/**
447457
* Mapped to toolTip property of NSView. Used to show extra information when
448458
* mouse hovering.

Libraries/Text/TextInput/RCTBaseTextInputView.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
3939
_eventDispatcher = bridge.eventDispatcher;
4040

4141
self.clipsToBounds = YES;
42+
self.cursor = RCTCursorText;
4243
}
4344

4445
return self;

Libraries/Text/TextInput/Singleline/RCTUITextField.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ - (void)textDidEndEditing:(NSNotification *)notification
111111
}
112112
}
113113

114+
// Do nothing here, as it messes with RCTWindow cursor support.
115+
- (void)resetCursorRects {}
116+
114117
- (BOOL)becomeFirstResponder
115118
{
116119
if ([super becomeFirstResponder]) {

React/Base/RCTConvert.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#import <React/RCTAnimationType.h>
1414
#import <React/RCTBlendMode.h>
1515
#import <React/RCTBorderStyle.h>
16+
#import <React/RCTCursor.h>
1617
#import <React/RCTDefines.h>
1718
#import <React/RCTLog.h>
1819
#import <React/RCTPointerEvents.h>
@@ -114,6 +115,7 @@ typedef BOOL css_backface_visibility_t;
114115
+ (RCTAnimationType)RCTAnimationType:(id)json;
115116
+ (RCTBlendMode)RCTBlendMode:(id)json;
116117
+ (RCTBorderStyle)RCTBorderStyle:(id)json;
118+
+ (RCTCursor)RCTCursor:(id)json;
117119
+ (RCTTextDecorationLineType)RCTTextDecorationLineType:(id)json;
118120

119121
@end

React/Base/RCTConvert.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,17 @@ + (NSPropertyList)NSPropertyList:(id)json
645645
@"overlay": @(RCTBlendModeOverlay),
646646
}), RCTBlendModeNone, integerValue)
647647

648+
RCT_ENUM_CONVERTER(RCTCursor, (@{
649+
@"inherit": @(RCTCursorInherit),
650+
@"none": @(RCTCursorNone),
651+
@"default": @(RCTCursorDefault),
652+
@"pointer": @(RCTCursorPointer),
653+
@"text": @(RCTCursorText),
654+
@"move": @(RCTCursorMove),
655+
@"grab": @(RCTCursorGrab),
656+
@"grabbing": @(RCTCursorGrabbing),
657+
}), RCTCursorInherit, integerValue)
658+
648659
@end
649660

650661
@interface RCTImageSource (Packager)

React/Modules/RCTCursorManager.m

Lines changed: 0 additions & 42 deletions
This file was deleted.

React/React.xcodeproj/project.pbxproj

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,8 @@
529529
70347E2122FF448600A4F09B /* RCTGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 70347E2022FF448600A4F09B /* RCTGradient.m */; };
530530
70347E2322FF44B700A4F09B /* RCTGradientManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 70347E2222FF44B700A4F09B /* RCTGradientManager.m */; };
531531
70347E2522FF44C100A4F09B /* RCTGradientManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 70347E2422FF44BF00A4F09B /* RCTGradientManager.h */; };
532+
7045C32223038208000AB285 /* RCTCursor.h in Headers */ = {isa = PBXBuildFile; fileRef = 7045C32123038208000AB285 /* RCTCursor.h */; };
533+
7045C323230384FE000AB285 /* RCTCursor.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 7045C32123038208000AB285 /* RCTCursor.h */; };
532534
705EDE2922107DD0000CAA67 /* Utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 705EDE2722107DD0000CAA67 /* Utils.h */; };
533535
705EDE2A22107DD0000CAA67 /* Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 705EDE2822107DD0000CAA67 /* Utils.cpp */; };
534536
705EDE2C221082CB000CAA67 /* RCTSurfaceSizeMeasureMode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 705EDE2B221082CB000CAA67 /* RCTSurfaceSizeMeasureMode.mm */; };
@@ -599,8 +601,6 @@
599601
CF2731C11E7B8DE40044CA4F /* RCTDeviceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = CF2731BF1E7B8DE40044CA4F /* RCTDeviceInfo.m */; };
600602
D426ACD020D57642003B4C4D /* RCTAppearance.h in Headers */ = {isa = PBXBuildFile; fileRef = D426ACCF20D57642003B4C4D /* RCTAppearance.h */; };
601603
D426ACD220D57659003B4C4D /* RCTAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = D426ACD120D57659003B4C4D /* RCTAppearance.m */; };
602-
D49593DE202C937C00A7694B /* RCTCursorManager.h in Headers */ = {isa = PBXBuildFile; fileRef = D49593D8202C937B00A7694B /* RCTCursorManager.h */; };
603-
D49593DF202C937C00A7694B /* RCTCursorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D49593D9202C937B00A7694B /* RCTCursorManager.m */; };
604604
D49593E0202C937C00A7694B /* RCTMenuManager.h in Headers */ = {isa = PBXBuildFile; fileRef = D49593DA202C937B00A7694B /* RCTMenuManager.h */; };
605605
D49593E1202C937C00A7694B /* RCTMenuManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D49593DB202C937C00A7694B /* RCTMenuManager.m */; };
606606
D49593E7202C970600A7694B /* YGNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D49593E3202C96FF00A7694B /* YGNode.h */; };
@@ -704,6 +704,7 @@
704704
dstPath = include/React;
705705
dstSubfolderSpec = 16;
706706
files = (
707+
7045C323230384FE000AB285 /* RCTCursor.h in Copy Headers */,
707708
70CDF31522F9E3BE00ECA452 /* RCTBlendMode.h in Copy Headers */,
708709
7095B6D02247C86300BE2245 /* RCTFieldEditor.h in Copy Headers */,
709710
702B7FFF221C88D70027174A /* RCTWindow.h in Copy Headers */,
@@ -1286,6 +1287,7 @@
12861287
70347E2022FF448600A4F09B /* RCTGradient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTGradient.m; sourceTree = "<group>"; };
12871288
70347E2222FF44B700A4F09B /* RCTGradientManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTGradientManager.m; sourceTree = "<group>"; };
12881289
70347E2422FF44BF00A4F09B /* RCTGradientManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTGradientManager.h; sourceTree = "<group>"; };
1290+
7045C32123038208000AB285 /* RCTCursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTCursor.h; sourceTree = "<group>"; };
12891291
705EDE2722107DD0000CAA67 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = "<group>"; };
12901292
705EDE2822107DD0000CAA67 /* Utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Utils.cpp; sourceTree = "<group>"; };
12911293
705EDE2B221082CB000CAA67 /* RCTSurfaceSizeMeasureMode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTSurfaceSizeMeasureMode.mm; sourceTree = "<group>"; };
@@ -1362,8 +1364,6 @@
13621364
CF2731BF1E7B8DE40044CA4F /* RCTDeviceInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDeviceInfo.m; sourceTree = "<group>"; };
13631365
D426ACCF20D57642003B4C4D /* RCTAppearance.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTAppearance.h; sourceTree = "<group>"; };
13641366
D426ACD120D57659003B4C4D /* RCTAppearance.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTAppearance.m; sourceTree = "<group>"; };
1365-
D49593D8202C937B00A7694B /* RCTCursorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCursorManager.h; sourceTree = "<group>"; };
1366-
D49593D9202C937B00A7694B /* RCTCursorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCursorManager.m; sourceTree = "<group>"; };
13671367
D49593DA202C937B00A7694B /* RCTMenuManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMenuManager.h; sourceTree = "<group>"; };
13681368
D49593DB202C937C00A7694B /* RCTMenuManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMenuManager.m; sourceTree = "<group>"; };
13691369
D49593E3202C96FF00A7694B /* YGNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YGNode.h; sourceTree = "<group>"; };
@@ -1562,8 +1562,6 @@
15621562
13B07FE01A69315300A75B9A /* Modules */ = {
15631563
isa = PBXGroup;
15641564
children = (
1565-
D49593D8202C937B00A7694B /* RCTCursorManager.h */,
1566-
D49593D9202C937B00A7694B /* RCTCursorManager.m */,
15671565
D49593DA202C937B00A7694B /* RCTMenuManager.h */,
15681566
D49593DB202C937C00A7694B /* RCTMenuManager.m */,
15691567
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */,
@@ -1638,6 +1636,7 @@
16381636
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */,
16391637
130443C31E401A8C00D93A67 /* RCTConvert+Transform.h */,
16401638
130443C41E401A8C00D93A67 /* RCTConvert+Transform.m */,
1639+
7045C32123038208000AB285 /* RCTCursor.h */,
16411640
133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */,
16421641
133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */,
16431642
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
@@ -2251,6 +2250,7 @@
22512250
3D80DA1E1DF820620028D040 /* RCTNetworkTask.h in Headers */,
22522251
657734841EE834C900A0E9EA /* RCTInspectorDevServerHelper.h in Headers */,
22532252
3D80DA1F1DF820620028D040 /* RCTPushNotificationManager.h in Headers */,
2253+
7045C32223038208000AB285 /* RCTCursor.h in Headers */,
22542254
3D80DA211DF820620028D040 /* RCTBridge.h in Headers */,
22552255
13F880381E296D2800C3C7A1 /* JSCWrapper.h in Headers */,
22562256
3D80DA221DF820620028D040 /* RCTBridge+Private.h in Headers */,
@@ -2382,7 +2382,6 @@
23822382
657734901EE8354A00A0E9EA /* RCTInspectorPackagerConnection.h in Headers */,
23832383
3D7BFD1D1EA8E351008DFB7A /* RCTPackagerConnection.h in Headers */,
23842384
3D80DA811DF820620028D040 /* RCTSegmentedControl.h in Headers */,
2385-
D49593DE202C937C00A7694B /* RCTCursorManager.h in Headers */,
23862385
599FAA3C1FB274980058CCF6 /* RCTSurfaceRootShadowView.h in Headers */,
23872386
3D80DA821DF820620028D040 /* RCTSegmentedControlManager.h in Headers */,
23882387
3D80DA831DF820620028D040 /* RCTShadowView.h in Headers */,
@@ -2849,7 +2848,6 @@
28492848
3D7BFD1F1EA8E351008DFB7A /* RCTPackagerConnection.mm in Sources */,
28502849
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */,
28512850
70347E2322FF44B700A4F09B /* RCTGradientManager.m in Sources */,
2852-
D49593DF202C937C00A7694B /* RCTCursorManager.m in Sources */,
28532851
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
28542852
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
28552853
130443C61E401A8C00D93A67 /* RCTConvert+Transform.m in Sources */,

React/Views/NSView+React.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import <AppKit/AppKit.h>
1111

1212
#import <React/RCTComponent.h>
13+
#import <React/RCTCursor.h>
1314
#import <yoga/YGEnums.h>
1415

1516
@class RCTShadowView;
@@ -119,6 +120,9 @@
119120
/** Populate the `layer` ivar when nil */
120121
- (void)ensureLayerExists;
121122

123+
/** Default implementation to avoid crashes */
124+
- (RCTCursor)cursor;
125+
122126
@end
123127

124128
@interface CALayer (React)

React/Views/NSView+React.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,12 @@ - (void)setTransform:(__unused CATransform3D)transform
329329
// override "displayLayer:", and apply the transform there.
330330
}
331331

332+
// Default implementation for React-managed views.
333+
- (RCTCursor)cursor
334+
{
335+
return RCTCursorInherit;
336+
}
337+
332338
@end
333339

334340
#pragma mark -

React/Modules/RCTCursorManager.h renamed to React/Views/RCTCursor.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,21 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10-
#import <AppKit/AppKit.h>
11-
#import "RCTBridgeModule.h"
10+
#import <Foundation/Foundation.h>
11+
12+
@interface NSCursor (Private)
13+
14+
+ (NSCursor *)_moveCursor;
1215

13-
@interface RCTCursorManager : NSObject <RCTBridgeModule>
1416
@end
17+
18+
typedef NS_ENUM(NSInteger, RCTCursor) {
19+
RCTCursorInherit = 0,
20+
RCTCursorNone,
21+
RCTCursorDefault,
22+
RCTCursorPointer,
23+
RCTCursorText,
24+
RCTCursorMove,
25+
RCTCursorGrab,
26+
RCTCursorGrabbing,
27+
};

React/Views/RCTView.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#import <React/RCTBorderStyle.h>
1313
#import <React/RCTComponent.h>
14+
#import <React/RCTCursor.h>
1415
#import <React/RCTPointerEvents.h>
1516
#import <React/RCTView.h>
1617

@@ -129,4 +130,9 @@
129130
@property (nonatomic, copy) RCTDirectEventBlock onDrop;
130131
@property (nonatomic, copy) RCTDirectEventBlock onContextMenuItemClick;
131132

133+
/**
134+
* The cursor image to show while the mouse is inside this view.
135+
*/
136+
@property (nonatomic, assign) RCTCursor cursor;
137+
132138
@end

React/Views/RCTViewManager.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ - (RCTShadowView *)shadowView
111111
RCT_EXPORT_LAYER_PROPERTY(shadowOffset, CGSize)
112112
RCT_EXPORT_LAYER_PROPERTY(shadowOpacity, float)
113113
RCT_EXPORT_LAYER_PROPERTY(shadowRadius, CGFloat)
114-
RCT_REMAP_VIEW_PROPERTY(toolTip, toolTip, NSString)
114+
RCT_EXPORT_VIEW_PROPERTY(cursor, RCTCursor)
115+
RCT_EXPORT_VIEW_PROPERTY(toolTip, NSString)
115116
RCT_CUSTOM_VIEW_PROPERTY(overflow, YGOverflow, RCTView)
116117
{
117118
if (json) {

React/Views/RCTWindow.m

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import "RCTWindow.h"
1111

1212
#import "RCTUtils.h"
13+
#import "RCTCursor.h"
1314
#import "RCTMouseEvent.h"
1415
#import "RCTTouchEvent.h"
1516
#import "RCTFieldEditor.h"
@@ -19,6 +20,7 @@ @implementation RCTWindow
1920
{
2021
RCTBridge *_bridge;
2122

23+
RCTCursor _lastCursor;
2224
NSMutableDictionary *_mouseInfo;
2325
NSView *_clickTarget;
2426
NSEventType _clickType;
@@ -256,6 +258,53 @@ - (NSView *)hitTest:(NSPoint)point withEvent:(NSEvent *)event
256258
return targetView;
257259
}
258260

261+
static NSCursor *NSCursorForRCTCursor(RCTCursor cursor)
262+
{
263+
switch (cursor) {
264+
case RCTCursorDefault: return NSCursor.arrowCursor;
265+
case RCTCursorPointer: return NSCursor.pointingHandCursor;
266+
case RCTCursorText: return NSCursor.IBeamCursor;
267+
case RCTCursorMove: return NSCursor._moveCursor;
268+
case RCTCursorGrab: return NSCursor.openHandCursor;
269+
case RCTCursorGrabbing: return NSCursor.closedHandCursor;
270+
default: return nil;
271+
}
272+
}
273+
274+
// HACK: Do nothing here to prevent AppKit default behavior of updating the cursor whenever a view moves.
275+
- (void)_setCursorForMouseLocation:(__unused CGPoint)point {}
276+
277+
- (void)_updateCursor:(NSView *)view
278+
{
279+
// Find the nearest React-managed view with a defined cursor.
280+
while (view) {
281+
RCTCursor cursor = view.cursor;
282+
if (cursor != RCTCursorInherit) {
283+
if (cursor == _lastCursor) {
284+
return;
285+
}
286+
287+
if (cursor == RCTCursorNone) {
288+
[NSCursor hide];
289+
} else {
290+
[NSCursor unhide];
291+
[NSCursorForRCTCursor(cursor) set];
292+
}
293+
294+
_lastCursor = cursor;
295+
return;
296+
}
297+
view = view.superview;
298+
}
299+
// Reset the cursor to its default.
300+
if (_lastCursor != RCTCursorDefault) {
301+
_lastCursor = RCTCursorDefault;
302+
303+
[NSCursor unhide];
304+
[NSCursor.arrowCursor set];
305+
}
306+
}
307+
259308
- (void)_setHoverTarget:(NSView *)view
260309
{
261310
NSNumber *target = view.reactTag;
@@ -269,6 +318,9 @@ - (void)_setHoverTarget:(NSView *)view
269318

270319
_hoverTarget = nil;
271320
[self _sendMouseEvent:@"mouseOut"];
321+
if (!view) {
322+
[self _updateCursor:nil];
323+
}
272324
}
273325
}
274326

@@ -279,6 +331,7 @@ - (void)_setHoverTarget:(NSView *)view
279331
if (_hoverTarget == nil) {
280332
_hoverTarget = view;
281333
[self _sendMouseEvent:@"mouseOver"];
334+
[self _updateCursor:view];
282335

283336
// Ensure "mouseMove" events have no "relatedTarget" property.
284337
_mouseInfo[@"relatedTarget"] = nil;

0 commit comments

Comments
 (0)