not-react-native-macos 0.87.1-rc.4 → 0.87.1-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Libraries/Components/View/ViewPropTypes.js +100 -0
- package/Libraries/NativeComponent/BaseViewConfig.macos.js +9 -0
- package/Libraries/Types/CoreEventTypes.js +11 -0
- package/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +325 -0
- package/React/React-RCTFabric.podspec +4 -0
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp +96 -1
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h +20 -11
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEvents.h +7 -2
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp +16 -0
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.h +18 -0
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewTraitsInitializer.h +2 -1
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/KeyEvent.h +100 -0
- package/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MouseEvent.h +81 -0
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
LayoutChangeEvent,
|
|
23
23
|
LayoutRectangle,
|
|
24
24
|
MouseEvent,
|
|
25
|
+
NativeSyntheticEvent, // [macOS] DragEvent below is built on it
|
|
25
26
|
PointerEvent,
|
|
26
27
|
} from '../../Types/CoreEventTypes';
|
|
27
28
|
import type {
|
|
@@ -132,7 +133,106 @@ type FocusEventProps = Readonly<{
|
|
|
132
133
|
// a .macos file because ViewPropTypes is shared, and a prop that is simply
|
|
133
134
|
// absent on other platforms costs them nothing. The native side is
|
|
134
135
|
// HostPlatformViewProps under components/view/platform/macos. macOS]
|
|
136
|
+
/**
|
|
137
|
+
* A key the view means to act on itself. A modifier left out is a wildcard:
|
|
138
|
+
* `{key: 'c'}` matches Cmd-C and a bare c alike.
|
|
139
|
+
*
|
|
140
|
+
* @platform macos
|
|
141
|
+
*/
|
|
142
|
+
export type HandledKeyEvent = Readonly<{
|
|
143
|
+
key: string,
|
|
144
|
+
altKey?: ?boolean,
|
|
145
|
+
ctrlKey?: ?boolean,
|
|
146
|
+
metaKey?: ?boolean,
|
|
147
|
+
shiftKey?: ?boolean,
|
|
148
|
+
}>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* A dragged file, or dragged image data with no file behind it -- in which case
|
|
152
|
+
* `uri` is a data: URL rather than a path.
|
|
153
|
+
*
|
|
154
|
+
* @platform macos
|
|
155
|
+
*/
|
|
156
|
+
export type DataTransferFile = Readonly<{
|
|
157
|
+
name: string,
|
|
158
|
+
type: string,
|
|
159
|
+
uri: string,
|
|
160
|
+
size?: number,
|
|
161
|
+
width?: number,
|
|
162
|
+
height?: number,
|
|
163
|
+
}>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The pasteboard contents of a drag, shaped like the DOM DataTransfer.
|
|
167
|
+
*
|
|
168
|
+
* @platform macos
|
|
169
|
+
*/
|
|
170
|
+
export type DataTransfer = Readonly<{
|
|
171
|
+
files: $ReadOnlyArray<DataTransferFile>,
|
|
172
|
+
items: $ReadOnlyArray<Readonly<{kind: string, type: string}>>,
|
|
173
|
+
types: $ReadOnlyArray<string>,
|
|
174
|
+
}>;
|
|
175
|
+
|
|
176
|
+
export type DragEvent = NativeSyntheticEvent<
|
|
177
|
+
Readonly<{
|
|
178
|
+
clientX: number,
|
|
179
|
+
clientY: number,
|
|
180
|
+
pageX: number,
|
|
181
|
+
pageY: number,
|
|
182
|
+
screenX: number,
|
|
183
|
+
screenY: number,
|
|
184
|
+
altKey: boolean,
|
|
185
|
+
ctrlKey: boolean,
|
|
186
|
+
metaKey: boolean,
|
|
187
|
+
shiftKey: boolean,
|
|
188
|
+
button: number,
|
|
189
|
+
dataTransfer: DataTransfer,
|
|
190
|
+
}>,
|
|
191
|
+
>;
|
|
192
|
+
|
|
135
193
|
type MacOSViewProps = Readonly<{
|
|
194
|
+
/**
|
|
195
|
+
* What kinds of dragged content the view accepts: 'fileUrl', 'image',
|
|
196
|
+
* 'string'. Required for the drag handlers to fire at all -- AppKit routes a
|
|
197
|
+
* drag only to views that registered for one of the pasteboard types it
|
|
198
|
+
* carries.
|
|
199
|
+
*
|
|
200
|
+
* @platform macos
|
|
201
|
+
*/
|
|
202
|
+
draggedTypes?: ?$ReadOnlyArray<'fileUrl' | 'image' | 'string'>,
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Called as a drag enters, leaves, or is released over the view.
|
|
206
|
+
*
|
|
207
|
+
* @platform macos
|
|
208
|
+
*/
|
|
209
|
+
onDragEnter?: ?(event: DragEvent) => void,
|
|
210
|
+
|
|
211
|
+
/** @platform macos */
|
|
212
|
+
onDragLeave?: ?(event: DragEvent) => void,
|
|
213
|
+
|
|
214
|
+
/** @platform macos */
|
|
215
|
+
onDrop?: ?(event: DragEvent) => void,
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Keys this view handles itself, suppressing AppKit's own interpretation of
|
|
219
|
+
* them -- Tab moving focus, Escape cancelling, an unclaimed key beeping.
|
|
220
|
+
*
|
|
221
|
+
* Separate from `onKeyDown`, deliberately: listening to a key does not change
|
|
222
|
+
* what it does, so a view that observes Tab still lets Tab move focus. Only
|
|
223
|
+
* listing a key here claims it.
|
|
224
|
+
*
|
|
225
|
+
* @platform macos
|
|
226
|
+
*/
|
|
227
|
+
keyDownEvents?: ?$ReadOnlyArray<HandledKeyEvent>,
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* As `keyDownEvents`, for key release.
|
|
231
|
+
*
|
|
232
|
+
* @platform macos
|
|
233
|
+
*/
|
|
234
|
+
keyUpEvents?: ?$ReadOnlyArray<HandledKeyEvent>,
|
|
235
|
+
|
|
136
236
|
/**
|
|
137
237
|
* The view's help tag, shown when the pointer rests over it.
|
|
138
238
|
*
|
|
@@ -44,13 +44,19 @@ const directEventTypes = {
|
|
|
44
44
|
topAuxClick: {registrationName: 'onAuxClick'},
|
|
45
45
|
topMouseEnter: {registrationName: 'onMouseEnter'},
|
|
46
46
|
topMouseLeave: {registrationName: 'onMouseLeave'},
|
|
47
|
+
topDragEnter: {registrationName: 'onDragEnter'},
|
|
48
|
+
topDragLeave: {registrationName: 'onDragLeave'},
|
|
49
|
+
topDrop: {registrationName: 'onDrop'},
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
const validAttributesForNonEventProps = {
|
|
50
53
|
acceptsFirstMouse: true,
|
|
51
54
|
allowsVibrancy: true,
|
|
55
|
+
draggedTypes: true,
|
|
52
56
|
enableFocusRing: true,
|
|
53
57
|
focusable: true,
|
|
58
|
+
keyDownEvents: true,
|
|
59
|
+
keyUpEvents: true,
|
|
54
60
|
mouseDownCanMoveWindow: true,
|
|
55
61
|
tooltip: true,
|
|
56
62
|
};
|
|
@@ -58,6 +64,9 @@ const validAttributesForNonEventProps = {
|
|
|
58
64
|
const validAttributesForEventProps = ConditionallyIgnoredEventHandlers({
|
|
59
65
|
onAuxClick: true,
|
|
60
66
|
onDoubleClick: true,
|
|
67
|
+
onDragEnter: true,
|
|
68
|
+
onDragLeave: true,
|
|
69
|
+
onDrop: true,
|
|
61
70
|
onKeyDown: true,
|
|
62
71
|
onKeyUp: true,
|
|
63
72
|
onMouseEnter: true,
|
|
@@ -351,6 +351,17 @@ export type KeyEvent = Readonly<{
|
|
|
351
351
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent/isComposing
|
|
352
352
|
*/
|
|
353
353
|
isComposing?: boolean,
|
|
354
|
+
// [macOS] Modifiers AppKit reports and no other platform has. Optional, so
|
|
355
|
+
// shared code that never reads them is unaffected.
|
|
356
|
+
/** @platform macos */
|
|
357
|
+
capsLockKey?: boolean,
|
|
358
|
+
/** @platform macos */
|
|
359
|
+
numericPadKey?: boolean,
|
|
360
|
+
/** @platform macos */
|
|
361
|
+
helpKey?: boolean,
|
|
362
|
+
/** @platform macos */
|
|
363
|
+
functionKey?: boolean,
|
|
364
|
+
// macOS]
|
|
354
365
|
}>;
|
|
355
366
|
|
|
356
367
|
export type KeyUpEvent = NativeSyntheticEvent<KeyEvent>;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
#import <CoreGraphics/CoreGraphics.h>
|
|
12
12
|
#import <QuartzCore/QuartzCore.h>
|
|
13
|
+
#import <algorithm> // [macOS] std::find, for matching a press against keyDownEvents
|
|
13
14
|
#import <objc/runtime.h>
|
|
14
15
|
#import <ranges>
|
|
15
16
|
|
|
@@ -22,6 +23,11 @@
|
|
|
22
23
|
#import <React/RCTLinearGradient.h>
|
|
23
24
|
#import <React/RCTLocalizedString.h>
|
|
24
25
|
#import <React/RCTRadialGradient.h>
|
|
26
|
+
#if TARGET_OS_OSX // [macOS] drag and drop: RCTDataURL for dragged image data,
|
|
27
|
+
// UTType to name what was dropped.
|
|
28
|
+
#import <React/RCTUtils.h>
|
|
29
|
+
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
|
30
|
+
#endif // macOS]
|
|
25
31
|
#import <react/featureflags/ReactNativeFeatureFlags.h>
|
|
26
32
|
#import <react/renderer/components/view/ViewComponentDescriptor.h>
|
|
27
33
|
#import <react/renderer/components/view/ViewEventEmitter.h>
|
|
@@ -663,6 +669,38 @@ static BOOL RCTLayerTransformCollapsesAxis(CALayer *layer)
|
|
|
663
669
|
if (oldViewProps.hostPlatformEvents != newViewProps.hostPlatformEvents) {
|
|
664
670
|
[self _updateMouseTracking:newViewProps.hostPlatformEvents.wantsMouseTracking()];
|
|
665
671
|
}
|
|
672
|
+
|
|
673
|
+
if (oldViewProps.draggedTypes != newViewProps.draggedTypes) {
|
|
674
|
+
[self _updateDraggedTypes:newViewProps.draggedTypes];
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* AppKit delivers a drag only to views that registered for one of the
|
|
680
|
+
* pasteboard types it carries, so without this the drag handlers are
|
|
681
|
+
* unreachable. The three names are the kinds React Native models; each maps to
|
|
682
|
+
* the pasteboard types AppKit actually uses for it.
|
|
683
|
+
*/
|
|
684
|
+
- (void)_updateDraggedTypes:(const std::vector<std::string> &)draggedTypes
|
|
685
|
+
{
|
|
686
|
+
[self unregisterDraggedTypes];
|
|
687
|
+
|
|
688
|
+
if (draggedTypes.empty()) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
NSMutableArray<NSPasteboardType> *types = [NSMutableArray arrayWithCapacity:draggedTypes.size()];
|
|
693
|
+
for (const auto &draggedType : draggedTypes) {
|
|
694
|
+
if (draggedType == "fileUrl") {
|
|
695
|
+
[types addObject:NSPasteboardTypeFileURL];
|
|
696
|
+
} else if (draggedType == "image") {
|
|
697
|
+
[types addObject:NSPasteboardTypePNG];
|
|
698
|
+
[types addObject:NSPasteboardTypeTIFF];
|
|
699
|
+
} else if (draggedType == "string") {
|
|
700
|
+
[types addObject:NSPasteboardTypeString];
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
[self registerForDraggedTypes:types];
|
|
666
704
|
}
|
|
667
705
|
|
|
668
706
|
/**
|
|
@@ -740,6 +778,277 @@ static BOOL RCTLayerTransformCollapsesAxis(CALayer *layer)
|
|
|
740
778
|
}
|
|
741
779
|
}
|
|
742
780
|
|
|
781
|
+
#pragma mark - Drag and Drop Events
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* The pasteboard, shaped like the DOM DataTransfer so a drop handler reads the
|
|
785
|
+
* same on macOS as on the web.
|
|
786
|
+
*
|
|
787
|
+
* Dragged files are reported by path. Dragged image *data* -- an image dragged
|
|
788
|
+
* out of a browser, say, with no file behind it -- has no path to give, so it
|
|
789
|
+
* is reported as a data: URL instead; `uri` is what a consumer feeds to
|
|
790
|
+
* <Image> either way.
|
|
791
|
+
*/
|
|
792
|
+
- (DataTransfer)_dataTransferForPasteboard:(NSPasteboard *)pasteboard
|
|
793
|
+
{
|
|
794
|
+
DataTransfer dataTransfer{};
|
|
795
|
+
|
|
796
|
+
NSArray<NSURL *> *fileURLs = [pasteboard readObjectsForClasses:@[ [NSURL class] ]
|
|
797
|
+
options:@{NSPasteboardURLReadingFileURLsOnlyKey : @YES}]
|
|
798
|
+
?: @[];
|
|
799
|
+
|
|
800
|
+
for (NSURL *fileURL in fileURLs) {
|
|
801
|
+
BOOL isDirectory = NO;
|
|
802
|
+
if (![NSFileManager.defaultManager fileExistsAtPath:fileURL.path isDirectory:&isDirectory] || isDirectory) {
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
UTType *type = [UTType typeWithFilenameExtension:fileURL.pathExtension];
|
|
807
|
+
NSString *mimeType = type.preferredMIMEType;
|
|
808
|
+
std::string typeString = mimeType != nil ? mimeType.UTF8String : "";
|
|
809
|
+
|
|
810
|
+
DataTransferFile file = {
|
|
811
|
+
.name = fileURL.lastPathComponent != nil ? fileURL.lastPathComponent.UTF8String : "",
|
|
812
|
+
.type = typeString,
|
|
813
|
+
.uri = fileURL.path != nil ? fileURL.path.UTF8String : "",
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
NSNumber *fileSize = nil;
|
|
817
|
+
if ([fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL]) {
|
|
818
|
+
file.size = fileSize.intValue;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if ([mimeType hasPrefix:@"image/"]) {
|
|
822
|
+
NSImage *image = [[NSImage alloc] initWithContentsOfURL:fileURL];
|
|
823
|
+
CGImageRef cgImage = [image CGImageForProposedRect:NULL context:nil hints:nil];
|
|
824
|
+
if (cgImage != NULL) {
|
|
825
|
+
file.width = static_cast<int>(CGImageGetWidth(cgImage));
|
|
826
|
+
file.height = static_cast<int>(CGImageGetHeight(cgImage));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
dataTransfer.files.push_back(file);
|
|
831
|
+
dataTransfer.items.push_back({.kind = "file", .type = typeString});
|
|
832
|
+
dataTransfer.types.push_back(typeString);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
NSPasteboardType imageType = [pasteboard availableTypeFromArray:@[ NSPasteboardTypePNG, NSPasteboardTypeTIFF ]];
|
|
836
|
+
if (imageType != nil && fileURLs.count == 0) {
|
|
837
|
+
NSString *mimeType = [imageType isEqualToString:NSPasteboardTypePNG] ? UTTypePNG.preferredMIMEType
|
|
838
|
+
: UTTypeTIFF.preferredMIMEType;
|
|
839
|
+
NSData *imageData = [pasteboard dataForType:imageType];
|
|
840
|
+
std::string typeString = mimeType != nil ? mimeType.UTF8String : "";
|
|
841
|
+
|
|
842
|
+
NSString *dataURL = RCTDataURL(mimeType, imageData).absoluteString;
|
|
843
|
+
DataTransferFile file = {
|
|
844
|
+
.name = "",
|
|
845
|
+
.type = typeString,
|
|
846
|
+
.uri = dataURL != nil ? dataURL.UTF8String : "",
|
|
847
|
+
};
|
|
848
|
+
file.size = static_cast<int>(imageData.length);
|
|
849
|
+
|
|
850
|
+
NSImage *image = [[NSImage alloc] initWithData:imageData];
|
|
851
|
+
CGImageRef cgImage = [image CGImageForProposedRect:NULL context:nil hints:nil];
|
|
852
|
+
if (cgImage != NULL) {
|
|
853
|
+
file.width = static_cast<int>(CGImageGetWidth(cgImage));
|
|
854
|
+
file.height = static_cast<int>(CGImageGetHeight(cgImage));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
dataTransfer.files.push_back(file);
|
|
858
|
+
dataTransfer.items.push_back({.kind = "image", .type = typeString});
|
|
859
|
+
dataTransfer.types.push_back(typeString);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return dataTransfer;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
- (DragEvent)_dragEventFromDraggingInfo:(id<NSDraggingInfo>)info
|
|
866
|
+
{
|
|
867
|
+
NSPoint inWindow = info.draggingLocation;
|
|
868
|
+
NSPoint inView = [self convertPoint:inWindow fromView:nil];
|
|
869
|
+
NSEventModifierFlags flags = self.window.currentEvent.modifierFlags;
|
|
870
|
+
|
|
871
|
+
DragEvent event = {};
|
|
872
|
+
event.clientX = inView.x;
|
|
873
|
+
event.clientY = inView.y;
|
|
874
|
+
event.pageX = inWindow.x;
|
|
875
|
+
event.pageY = inWindow.y;
|
|
876
|
+
event.screenX = inWindow.x;
|
|
877
|
+
event.screenY = inWindow.y;
|
|
878
|
+
event.altKey = static_cast<bool>(flags & NSEventModifierFlagOption);
|
|
879
|
+
event.ctrlKey = static_cast<bool>(flags & NSEventModifierFlagControl);
|
|
880
|
+
event.shiftKey = static_cast<bool>(flags & NSEventModifierFlagShift);
|
|
881
|
+
event.metaKey = static_cast<bool>(flags & NSEventModifierFlagCommand);
|
|
882
|
+
event.dataTransfer = [self _dataTransferForPasteboard:info.draggingPasteboard];
|
|
883
|
+
return event;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
|
|
887
|
+
{
|
|
888
|
+
if (_eventEmitter != nullptr) {
|
|
889
|
+
_eventEmitter->onDragEnter([self _dragEventFromDraggingInfo:sender]);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// The answer is the cursor the user sees, so it has to reflect what this view
|
|
893
|
+
// would actually accept -- claiming a drag the pasteboard cannot satisfy shows
|
|
894
|
+
// a drop cursor over content that will refuse it.
|
|
895
|
+
if ([sender.draggingPasteboard availableTypeFromArray:self.registeredDraggedTypes] == nil) {
|
|
896
|
+
return NSDragOperationNone;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
NSDragOperation offered = sender.draggingSourceOperationMask;
|
|
900
|
+
if (offered & NSDragOperationLink) {
|
|
901
|
+
return NSDragOperationLink;
|
|
902
|
+
}
|
|
903
|
+
if (offered & NSDragOperationCopy) {
|
|
904
|
+
return NSDragOperationCopy;
|
|
905
|
+
}
|
|
906
|
+
return NSDragOperationNone;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
- (void)draggingExited:(id<NSDraggingInfo>)sender
|
|
910
|
+
{
|
|
911
|
+
if (_eventEmitter != nullptr) {
|
|
912
|
+
_eventEmitter->onDragLeave([self _dragEventFromDraggingInfo:sender]);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
|
|
917
|
+
{
|
|
918
|
+
if (_eventEmitter == nullptr) {
|
|
919
|
+
return NO;
|
|
920
|
+
}
|
|
921
|
+
_eventEmitter->onDrop([self _dragEventFromDraggingInfo:sender]);
|
|
922
|
+
return YES;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
#pragma mark - Keyboard Events
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* The W3C `key` name for a press, per https://www.w3.org/TR/uievents-key/.
|
|
929
|
+
*
|
|
930
|
+
* `charactersIgnoringModifiers` already gives the right answer for anything
|
|
931
|
+
* printable. The cases below are the ones where it gives a private-use unichar
|
|
932
|
+
* (the arrows, the function keys) or a control character (Return, Delete)
|
|
933
|
+
* instead of a name. Tab and Escape are matched on keyCode rather than
|
|
934
|
+
* character because AppKit reports them as \t and \e, which would otherwise
|
|
935
|
+
* arrive as those literal characters.
|
|
936
|
+
*
|
|
937
|
+
* Naming follows the cross-platform reconciliation react-native-windows and
|
|
938
|
+
* react-native-macos both use, so a key handler is portable between them.
|
|
939
|
+
*/
|
|
940
|
+
static NSString *RCTKeyFromNSEvent(NSEvent *event)
|
|
941
|
+
{
|
|
942
|
+
NSString *characters = event.charactersIgnoringModifiers;
|
|
943
|
+
unichar code = characters.length > 0 ? [characters characterAtIndex:0] : 0;
|
|
944
|
+
|
|
945
|
+
switch (event.keyCode) {
|
|
946
|
+
case 48:
|
|
947
|
+
return @"Tab";
|
|
948
|
+
case 53:
|
|
949
|
+
return @"Escape";
|
|
950
|
+
default:
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
switch (code) {
|
|
955
|
+
case NSEnterCharacter:
|
|
956
|
+
case NSNewlineCharacter:
|
|
957
|
+
case NSCarriageReturnCharacter:
|
|
958
|
+
return @"Enter";
|
|
959
|
+
case NSLeftArrowFunctionKey:
|
|
960
|
+
return @"ArrowLeft";
|
|
961
|
+
case NSRightArrowFunctionKey:
|
|
962
|
+
return @"ArrowRight";
|
|
963
|
+
case NSUpArrowFunctionKey:
|
|
964
|
+
return @"ArrowUp";
|
|
965
|
+
case NSDownArrowFunctionKey:
|
|
966
|
+
return @"ArrowDown";
|
|
967
|
+
case NSBackspaceCharacter:
|
|
968
|
+
case NSDeleteCharacter:
|
|
969
|
+
return @"Backspace";
|
|
970
|
+
case NSDeleteFunctionKey:
|
|
971
|
+
return @"Delete";
|
|
972
|
+
case NSHomeFunctionKey:
|
|
973
|
+
return @"Home";
|
|
974
|
+
case NSEndFunctionKey:
|
|
975
|
+
return @"End";
|
|
976
|
+
case NSPageUpFunctionKey:
|
|
977
|
+
return @"PageUp";
|
|
978
|
+
case NSPageDownFunctionKey:
|
|
979
|
+
return @"PageDown";
|
|
980
|
+
default:
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (code >= NSF1FunctionKey && code <= NSF12FunctionKey) {
|
|
985
|
+
return [NSString stringWithFormat:@"F%u", (unsigned)(code - NSF1FunctionKey + 1)];
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return characters;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Emits the press and reports whether the view claimed it.
|
|
993
|
+
*
|
|
994
|
+
* Claiming matters because AppKit interprets an unclaimed key itself once the
|
|
995
|
+
* responder chain is done with it: Tab moves focus, Escape cancels, anything
|
|
996
|
+
* else beeps. A view says which keys it means to act on through `keyDownEvents`
|
|
997
|
+
* / `keyUpEvents`, and only those suppress the default behaviour. Listening via
|
|
998
|
+
* `onKeyDown` alone deliberately does not, so observing a key does not change
|
|
999
|
+
* what it does.
|
|
1000
|
+
*/
|
|
1001
|
+
- (BOOL)_handleKeyboardEvent:(NSEvent *)event
|
|
1002
|
+
{
|
|
1003
|
+
NSEventModifierFlags flags = event.modifierFlags;
|
|
1004
|
+
KeyEvent keyEvent = {
|
|
1005
|
+
.key = RCTStringFromNSString(RCTKeyFromNSEvent(event)),
|
|
1006
|
+
.altKey = static_cast<bool>(flags & NSEventModifierFlagOption),
|
|
1007
|
+
.ctrlKey = static_cast<bool>(flags & NSEventModifierFlagControl),
|
|
1008
|
+
.shiftKey = static_cast<bool>(flags & NSEventModifierFlagShift),
|
|
1009
|
+
.metaKey = static_cast<bool>(flags & NSEventModifierFlagCommand),
|
|
1010
|
+
.capsLockKey = static_cast<bool>(flags & NSEventModifierFlagCapsLock),
|
|
1011
|
+
.numericPadKey = static_cast<bool>(flags & NSEventModifierFlagNumericPad),
|
|
1012
|
+
.helpKey = static_cast<bool>(flags & NSEventModifierFlagHelp),
|
|
1013
|
+
.functionKey = static_cast<bool>(flags & NSEventModifierFlagFunction),
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
BOOL isKeyDown = event.type == NSEventTypeKeyDown;
|
|
1017
|
+
const auto &viewProps = static_cast<const ViewProps &>(*_props);
|
|
1018
|
+
|
|
1019
|
+
// Calling super walks the responder chain, which is the view hierarchy, so
|
|
1020
|
+
// every ancestor view would emit the same press. Fabric bubbles the event
|
|
1021
|
+
// through the shadow tree on its own, so only the innermost view should emit.
|
|
1022
|
+
// The flag rides on the NSEvent because that is the one object the whole
|
|
1023
|
+
// chain shares.
|
|
1024
|
+
static const char kEmittedKey = 0;
|
|
1025
|
+
if (_eventEmitter != nullptr && !((NSNumber *)objc_getAssociatedObject(event, &kEmittedKey)).boolValue) {
|
|
1026
|
+
if (isKeyDown) {
|
|
1027
|
+
_eventEmitter->onKeyDown(keyEvent);
|
|
1028
|
+
} else {
|
|
1029
|
+
_eventEmitter->onKeyUp(keyEvent);
|
|
1030
|
+
}
|
|
1031
|
+
objc_setAssociatedObject(event, &kEmittedKey, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const auto &handled = isKeyDown ? viewProps.keyDownEvents : viewProps.keyUpEvents;
|
|
1035
|
+
return std::find(handled.cbegin(), handled.cend(), keyEvent) != handled.cend();
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
- (void)keyDown:(NSEvent *)event
|
|
1039
|
+
{
|
|
1040
|
+
if (![self _handleKeyboardEvent:event]) {
|
|
1041
|
+
[super keyDown:event];
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
- (void)keyUp:(NSEvent *)event
|
|
1046
|
+
{
|
|
1047
|
+
if (![self _handleKeyboardEvent:event]) {
|
|
1048
|
+
[super keyUp:event];
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
743
1052
|
- (BOOL)allowsVibrancy
|
|
744
1053
|
{
|
|
745
1054
|
return _allowsVibrancy;
|
|
@@ -1922,7 +2231,16 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
|
|
1922
2231
|
|
|
1923
2232
|
- (void)focus
|
|
1924
2233
|
{
|
|
2234
|
+
#if TARGET_OS_OSX // [macOS] On AppKit -becomeFirstResponder is the window
|
|
2235
|
+
// notifying the view that it happened, not the view asking. Sending it
|
|
2236
|
+
// directly changes nothing: -makeFirstResponder: is the request. Without
|
|
2237
|
+
// this, `focusable` views never receive key events, because nothing ever
|
|
2238
|
+
// makes them first responder -- AppKit moves focus on Tab only, and only
|
|
2239
|
+
// when Full Keyboard Access is on.
|
|
2240
|
+
[self.window makeFirstResponder:self];
|
|
2241
|
+
#else // [macOS]
|
|
1925
2242
|
[self becomeFirstResponder];
|
|
2243
|
+
#endif // [macOS]
|
|
1926
2244
|
|
|
1927
2245
|
#if TARGET_OS_TV
|
|
1928
2246
|
RCTSurfaceHostingProxyRootView *rootView = [self containingRootView];
|
|
@@ -1938,7 +2256,14 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
|
|
1938
2256
|
|
|
1939
2257
|
- (void)blur
|
|
1940
2258
|
{
|
|
2259
|
+
#if TARGET_OS_OSX // [macOS] Symmetrically: resigning is granted by the window,
|
|
2260
|
+
// and only if this view still holds the focus.
|
|
2261
|
+
if (self.window.firstResponder == self) {
|
|
2262
|
+
[self.window makeFirstResponder:nil];
|
|
2263
|
+
}
|
|
2264
|
+
#else // [macOS]
|
|
1941
2265
|
[self resignFirstResponder];
|
|
2266
|
+
#endif // [macOS]
|
|
1942
2267
|
}
|
|
1943
2268
|
|
|
1944
2269
|
- (BOOL)becomeFirstResponder
|
|
@@ -50,6 +50,10 @@ Pod::Spec.new do |s|
|
|
|
50
50
|
s.module_name = module_name
|
|
51
51
|
s.weak_framework = "JavaScriptCore"
|
|
52
52
|
s.framework = "MobileCoreServices"
|
|
53
|
+
# [macOS] Drag and drop names what was dropped with UTType. On iOS the symbols
|
|
54
|
+
# come in with MobileCoreServices; the macOS remap of that is CoreServices,
|
|
55
|
+
# which does not carry them.
|
|
56
|
+
s.osx.framework = "UniformTypeIdentifiers"
|
|
53
57
|
s.pod_target_xcconfig = {
|
|
54
58
|
"HEADER_SEARCH_PATHS" => header_search_paths,
|
|
55
59
|
"OTHER_CFLAGS" => "$(inherited) " + new_arch_flags,
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
namespace facebook::react {
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Returns an Object, not a Value: dragEventPayload starts from one and adds to it.
|
|
15
|
+
static jsi::Object mouseEventPayload(jsi::Runtime &runtime, const MouseEvent &event)
|
|
15
16
|
{
|
|
16
17
|
auto payload = jsi::Object(runtime);
|
|
17
18
|
payload.setProperty(runtime, "clientX", event.clientX);
|
|
@@ -20,6 +21,30 @@ static jsi::Value mouseEventPayload(jsi::Runtime &runtime, const HostPlatformVie
|
|
|
20
21
|
payload.setProperty(runtime, "screenY", event.screenY);
|
|
21
22
|
payload.setProperty(runtime, "pageX", event.pageX);
|
|
22
23
|
payload.setProperty(runtime, "pageY", event.pageY);
|
|
24
|
+
payload.setProperty(runtime, "altKey", event.altKey);
|
|
25
|
+
payload.setProperty(runtime, "ctrlKey", event.ctrlKey);
|
|
26
|
+
payload.setProperty(runtime, "shiftKey", event.shiftKey);
|
|
27
|
+
payload.setProperty(runtime, "metaKey", event.metaKey);
|
|
28
|
+
payload.setProperty(runtime, "button", event.button);
|
|
29
|
+
// Pressability's click guard reads this to tell a real mouse click apart from
|
|
30
|
+
// one synthesised by the responder system, which would otherwise fire onPress
|
|
31
|
+
// twice.
|
|
32
|
+
payload.setProperty(runtime, "pointerType", "mouse");
|
|
33
|
+
return payload;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static jsi::Value keyEventPayload(jsi::Runtime &runtime, const KeyEvent &event)
|
|
37
|
+
{
|
|
38
|
+
auto payload = jsi::Object(runtime);
|
|
39
|
+
payload.setProperty(runtime, "key", jsi::String::createFromUtf8(runtime, event.key));
|
|
40
|
+
payload.setProperty(runtime, "altKey", event.altKey);
|
|
41
|
+
payload.setProperty(runtime, "ctrlKey", event.ctrlKey);
|
|
42
|
+
payload.setProperty(runtime, "shiftKey", event.shiftKey);
|
|
43
|
+
payload.setProperty(runtime, "metaKey", event.metaKey);
|
|
44
|
+
payload.setProperty(runtime, "capsLockKey", event.capsLockKey);
|
|
45
|
+
payload.setProperty(runtime, "numericPadKey", event.numericPadKey);
|
|
46
|
+
payload.setProperty(runtime, "helpKey", event.helpKey);
|
|
47
|
+
payload.setProperty(runtime, "functionKey", event.functionKey);
|
|
23
48
|
return payload;
|
|
24
49
|
}
|
|
25
50
|
|
|
@@ -36,4 +61,74 @@ RCT_MACOS_MOUSE_EVENT(onMouseLeave)
|
|
|
36
61
|
RCT_MACOS_MOUSE_EVENT(onDoubleClick)
|
|
37
62
|
RCT_MACOS_MOUSE_EVENT(onAuxClick)
|
|
38
63
|
|
|
64
|
+
#define RCT_MACOS_KEY_EVENT(name) \
|
|
65
|
+
void HostPlatformViewEventEmitter::name(const KeyEvent &event) const \
|
|
66
|
+
{ \
|
|
67
|
+
dispatchEvent(#name, [event](jsi::Runtime &runtime) { \
|
|
68
|
+
return keyEventPayload(runtime, event); \
|
|
69
|
+
}); \
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
RCT_MACOS_KEY_EVENT(onKeyDown)
|
|
73
|
+
RCT_MACOS_KEY_EVENT(onKeyUp)
|
|
74
|
+
|
|
75
|
+
static jsi::Value dragEventPayload(jsi::Runtime &runtime, const DragEvent &event)
|
|
76
|
+
{
|
|
77
|
+
auto dataTransfer = jsi::Object(runtime);
|
|
78
|
+
|
|
79
|
+
auto files = jsi::Array(runtime, event.dataTransfer.files.size());
|
|
80
|
+
for (size_t i = 0; i < event.dataTransfer.files.size(); i++) {
|
|
81
|
+
const auto &file = event.dataTransfer.files[i];
|
|
82
|
+
auto entry = jsi::Object(runtime);
|
|
83
|
+
entry.setProperty(runtime, "name", jsi::String::createFromUtf8(runtime, file.name));
|
|
84
|
+
entry.setProperty(runtime, "type", jsi::String::createFromUtf8(runtime, file.type));
|
|
85
|
+
entry.setProperty(runtime, "uri", jsi::String::createFromUtf8(runtime, file.uri));
|
|
86
|
+
// Absent rather than zero when unknown: a directory has no meaningful size,
|
|
87
|
+
// and only an image has dimensions.
|
|
88
|
+
if (file.size.has_value()) {
|
|
89
|
+
entry.setProperty(runtime, "size", *file.size);
|
|
90
|
+
}
|
|
91
|
+
if (file.width.has_value()) {
|
|
92
|
+
entry.setProperty(runtime, "width", *file.width);
|
|
93
|
+
}
|
|
94
|
+
if (file.height.has_value()) {
|
|
95
|
+
entry.setProperty(runtime, "height", *file.height);
|
|
96
|
+
}
|
|
97
|
+
files.setValueAtIndex(runtime, i, entry);
|
|
98
|
+
}
|
|
99
|
+
dataTransfer.setProperty(runtime, "files", files);
|
|
100
|
+
|
|
101
|
+
auto items = jsi::Array(runtime, event.dataTransfer.items.size());
|
|
102
|
+
for (size_t i = 0; i < event.dataTransfer.items.size(); i++) {
|
|
103
|
+
const auto &item = event.dataTransfer.items[i];
|
|
104
|
+
auto entry = jsi::Object(runtime);
|
|
105
|
+
entry.setProperty(runtime, "kind", jsi::String::createFromUtf8(runtime, item.kind));
|
|
106
|
+
entry.setProperty(runtime, "type", jsi::String::createFromUtf8(runtime, item.type));
|
|
107
|
+
items.setValueAtIndex(runtime, i, entry);
|
|
108
|
+
}
|
|
109
|
+
dataTransfer.setProperty(runtime, "items", items);
|
|
110
|
+
|
|
111
|
+
auto types = jsi::Array(runtime, event.dataTransfer.types.size());
|
|
112
|
+
for (size_t i = 0; i < event.dataTransfer.types.size(); i++) {
|
|
113
|
+
types.setValueAtIndex(runtime, i, jsi::String::createFromUtf8(runtime, event.dataTransfer.types[i]));
|
|
114
|
+
}
|
|
115
|
+
dataTransfer.setProperty(runtime, "types", types);
|
|
116
|
+
|
|
117
|
+
auto payload = mouseEventPayload(runtime, event);
|
|
118
|
+
payload.setProperty(runtime, "dataTransfer", dataTransfer);
|
|
119
|
+
return payload;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#define RCT_MACOS_DRAG_EVENT(name) \
|
|
123
|
+
void HostPlatformViewEventEmitter::name(const DragEvent &event) const \
|
|
124
|
+
{ \
|
|
125
|
+
dispatchEvent(#name, [event](jsi::Runtime &runtime) { \
|
|
126
|
+
return dragEventPayload(runtime, event); \
|
|
127
|
+
}); \
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
RCT_MACOS_DRAG_EVENT(onDragEnter)
|
|
131
|
+
RCT_MACOS_DRAG_EVENT(onDragLeave)
|
|
132
|
+
RCT_MACOS_DRAG_EVENT(onDrop)
|
|
133
|
+
|
|
39
134
|
} // namespace facebook::react
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
// [macOS] Adds the pointer events AppKit can report and UIKit cannot.
|
|
8
|
+
// [macOS] Adds the pointer and key events AppKit can report and UIKit cannot.
|
|
9
9
|
|
|
10
10
|
#pragma once
|
|
11
11
|
|
|
12
12
|
#include <react/renderer/components/view/BaseViewEventEmitter.h>
|
|
13
13
|
|
|
14
|
+
#include "KeyEvent.h"
|
|
15
|
+
#include "MouseEvent.h"
|
|
16
|
+
|
|
14
17
|
namespace facebook::react {
|
|
15
18
|
|
|
16
19
|
class HostPlatformViewEventEmitter : public BaseViewEventEmitter {
|
|
@@ -18,22 +21,28 @@ class HostPlatformViewEventEmitter : public BaseViewEventEmitter {
|
|
|
18
21
|
using BaseViewEventEmitter::BaseViewEventEmitter;
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
24
|
+
* Named here as well so `HostPlatformViewEventEmitter::MouseEvent` keeps
|
|
25
|
+
* working; the type itself is free-standing, in MouseEvent.h.
|
|
23
26
|
*/
|
|
24
|
-
|
|
25
|
-
Float clientX{};
|
|
26
|
-
Float clientY{};
|
|
27
|
-
Float screenX{};
|
|
28
|
-
Float screenY{};
|
|
29
|
-
Float pageX{};
|
|
30
|
-
Float pageY{};
|
|
31
|
-
};
|
|
27
|
+
using MouseEvent = ::facebook::react::MouseEvent;
|
|
32
28
|
|
|
33
29
|
void onMouseEnter(const MouseEvent &event) const;
|
|
34
30
|
void onMouseLeave(const MouseEvent &event) const;
|
|
35
31
|
void onDoubleClick(const MouseEvent &event) const;
|
|
36
32
|
void onAuxClick(const MouseEvent &event) const;
|
|
33
|
+
|
|
34
|
+
void onKeyDown(const KeyEvent &event) const;
|
|
35
|
+
void onKeyUp(const KeyEvent &event) const;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A drag passing over or finishing on the view. Only reported for views that
|
|
39
|
+
* registered interest through the `draggedTypes` prop -- AppKit routes a drag
|
|
40
|
+
* to the topmost view that registered for one of the pasteboard types it
|
|
41
|
+
* carries, and ignores the rest.
|
|
42
|
+
*/
|
|
43
|
+
void onDragEnter(const DragEvent &event) const;
|
|
44
|
+
void onDragLeave(const DragEvent &event) const;
|
|
45
|
+
void onDrop(const DragEvent &event) const;
|
|
37
46
|
};
|
|
38
47
|
|
|
39
48
|
} // namespace facebook::react
|
|
@@ -20,13 +20,18 @@
|
|
|
20
20
|
namespace facebook::react {
|
|
21
21
|
|
|
22
22
|
struct HostPlatformViewEvents {
|
|
23
|
-
std::bitset<
|
|
23
|
+
std::bitset<16> bits{};
|
|
24
24
|
|
|
25
25
|
enum class Offset : std::size_t {
|
|
26
26
|
MouseEnter = 0,
|
|
27
27
|
MouseLeave = 1,
|
|
28
28
|
DoubleClick = 2,
|
|
29
29
|
AuxClick = 3,
|
|
30
|
+
KeyDown = 4,
|
|
31
|
+
KeyUp = 5,
|
|
32
|
+
DragEnter = 6,
|
|
33
|
+
DragLeave = 7,
|
|
34
|
+
Drop = 8,
|
|
30
35
|
};
|
|
31
36
|
|
|
32
37
|
constexpr bool operator[](Offset offset) const
|
|
@@ -34,7 +39,7 @@ struct HostPlatformViewEvents {
|
|
|
34
39
|
return bits[static_cast<std::size_t>(offset)];
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
std::bitset<
|
|
42
|
+
std::bitset<16>::reference operator[](Offset offset)
|
|
38
43
|
{
|
|
39
44
|
return bits[static_cast<std::size_t>(offset)];
|
|
40
45
|
}
|
|
@@ -33,6 +33,11 @@ static inline HostPlatformViewEvents convertRawProp(
|
|
|
33
33
|
RCT_MACOS_CONVERT_EVENT("onMouseLeave", MouseLeave);
|
|
34
34
|
RCT_MACOS_CONVERT_EVENT("onDoubleClick", DoubleClick);
|
|
35
35
|
RCT_MACOS_CONVERT_EVENT("onAuxClick", AuxClick);
|
|
36
|
+
RCT_MACOS_CONVERT_EVENT("onKeyDown", KeyDown);
|
|
37
|
+
RCT_MACOS_CONVERT_EVENT("onKeyUp", KeyUp);
|
|
38
|
+
RCT_MACOS_CONVERT_EVENT("onDragEnter", DragEnter);
|
|
39
|
+
RCT_MACOS_CONVERT_EVENT("onDragLeave", DragLeave);
|
|
40
|
+
RCT_MACOS_CONVERT_EVENT("onDrop", Drop);
|
|
36
41
|
|
|
37
42
|
#undef RCT_MACOS_CONVERT_EVENT
|
|
38
43
|
|
|
@@ -59,6 +64,9 @@ HostPlatformViewProps::HostPlatformViewProps(
|
|
|
59
64
|
: convertRawProp(context, rawProps, sourceProps.hostPlatformEvents, {})),
|
|
60
65
|
RCT_MACOS_PROP(focusable),
|
|
61
66
|
RCT_MACOS_PROP(enableFocusRing),
|
|
67
|
+
RCT_MACOS_PROP(keyDownEvents),
|
|
68
|
+
RCT_MACOS_PROP(keyUpEvents),
|
|
69
|
+
RCT_MACOS_PROP(draggedTypes),
|
|
62
70
|
RCT_MACOS_PROP(tooltip),
|
|
63
71
|
RCT_MACOS_PROP(acceptsFirstMouse),
|
|
64
72
|
RCT_MACOS_PROP(allowsVibrancy),
|
|
@@ -99,8 +107,16 @@ void HostPlatformViewProps::setProp(
|
|
|
99
107
|
RCT_MACOS_EVENT_CASE(MouseLeave);
|
|
100
108
|
RCT_MACOS_EVENT_CASE(DoubleClick);
|
|
101
109
|
RCT_MACOS_EVENT_CASE(AuxClick);
|
|
110
|
+
RCT_MACOS_EVENT_CASE(KeyDown);
|
|
111
|
+
RCT_MACOS_EVENT_CASE(KeyUp);
|
|
112
|
+
RCT_MACOS_EVENT_CASE(DragEnter);
|
|
113
|
+
RCT_MACOS_EVENT_CASE(DragLeave);
|
|
114
|
+
RCT_MACOS_EVENT_CASE(Drop);
|
|
102
115
|
RAW_SET_PROP_SWITCH_CASE_BASIC(focusable);
|
|
103
116
|
RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing);
|
|
117
|
+
RAW_SET_PROP_SWITCH_CASE_BASIC(keyDownEvents);
|
|
118
|
+
RAW_SET_PROP_SWITCH_CASE_BASIC(keyUpEvents);
|
|
119
|
+
RAW_SET_PROP_SWITCH_CASE_BASIC(draggedTypes);
|
|
104
120
|
RAW_SET_PROP_SWITCH_CASE_BASIC(tooltip);
|
|
105
121
|
RAW_SET_PROP_SWITCH_CASE_BASIC(acceptsFirstMouse);
|
|
106
122
|
RAW_SET_PROP_SWITCH_CASE_BASIC(allowsVibrancy);
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
|
|
21
21
|
#include <optional>
|
|
22
22
|
#include <string>
|
|
23
|
+
#include <vector>
|
|
23
24
|
|
|
24
25
|
#include "HostPlatformViewEvents.h"
|
|
26
|
+
#include "KeyEvent.h"
|
|
25
27
|
|
|
26
28
|
namespace facebook::react {
|
|
27
29
|
|
|
@@ -47,6 +49,22 @@ class HostPlatformViewProps : public BaseViewProps {
|
|
|
47
49
|
/** Draws the focus ring while first responder. On by default, as in AppKit. */
|
|
48
50
|
bool enableFocusRing{true};
|
|
49
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Keys the view handles itself, rather than letting AppKit interpret them.
|
|
54
|
+
* See HandledKey: a press not declared here still reaches onKeyDown, but
|
|
55
|
+
* AppKit's own handling runs afterwards.
|
|
56
|
+
*/
|
|
57
|
+
std::vector<HandledKey> keyDownEvents{};
|
|
58
|
+
std::vector<HandledKey> keyUpEvents{};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Which kinds of dragged content the view accepts: "fileUrl", "image",
|
|
62
|
+
* "string". Empty means the view takes no part in drag and drop -- AppKit
|
|
63
|
+
* routes a drag only to views that registered for one of its pasteboard
|
|
64
|
+
* types, so this is what makes onDragEnter/onDrop reachable at all.
|
|
65
|
+
*/
|
|
66
|
+
std::vector<std::string> draggedTypes{};
|
|
67
|
+
|
|
50
68
|
/** The view's help tag. std::nullopt leaves any inherited tooltip alone. */
|
|
51
69
|
std::optional<std::string> tooltip{};
|
|
52
70
|
|
|
@@ -26,7 +26,8 @@ inline bool formsView(const ViewProps &props)
|
|
|
26
26
|
// flattened away: a focusable view has to join the key view loop, a tooltip
|
|
27
27
|
// has to attach to something, and mouse tracking needs an NSTrackingArea
|
|
28
28
|
// owner. Flattening is otherwise invisible -- the handler simply never fires.
|
|
29
|
-
return props.focusable || props.tooltip.has_value() || props.hostPlatformEvents.bits.any()
|
|
29
|
+
return props.focusable || props.tooltip.has_value() || props.hostPlatformEvents.bits.any() ||
|
|
30
|
+
!props.keyDownEvents.empty() || !props.keyUpEvents.empty() || !props.draggedTypes.empty();
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
inline bool isKeyboardFocusable(const ViewProps &props)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// [macOS] Key input, which only a desktop platform has to model.
|
|
9
|
+
//
|
|
10
|
+
// Two shapes, and the distinction matters. `KeyEvent` is what a view reports
|
|
11
|
+
// happened. `HandledKey` is what a view declares in advance that it wants,
|
|
12
|
+
// which is a filter, not an event: AppKit gives a key to the responder chain
|
|
13
|
+
// and then to its own interpretation -- Tab moves focus, Escape cancels, an
|
|
14
|
+
// unclaimed key beeps -- so a view that means to act on a key has to say so
|
|
15
|
+
// before the press, or the default behaviour happens as well.
|
|
16
|
+
//
|
|
17
|
+
// Names follow https://www.w3.org/TR/uievents-key/ rather than AppKit's
|
|
18
|
+
// keyCodes, so `keyDownEvents={[{key: 'Enter'}]}` is the same declaration it
|
|
19
|
+
// would be on the web, and matches microsoft/react-native-macos.
|
|
20
|
+
|
|
21
|
+
#pragma once
|
|
22
|
+
|
|
23
|
+
#include <react/renderer/core/PropsParserContext.h>
|
|
24
|
+
#include <react/renderer/core/propsConversions.h>
|
|
25
|
+
|
|
26
|
+
#include <optional>
|
|
27
|
+
#include <string>
|
|
28
|
+
#include <unordered_map>
|
|
29
|
+
|
|
30
|
+
namespace facebook::react {
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A key the view intends to handle itself.
|
|
34
|
+
*
|
|
35
|
+
* A modifier left unset means "don't care": `{key: 'c'}` matches Cmd-C and a
|
|
36
|
+
* bare c alike, while `{key: 'c', metaKey: true}` matches only the former.
|
|
37
|
+
*/
|
|
38
|
+
struct HandledKey {
|
|
39
|
+
std::string key{};
|
|
40
|
+
std::optional<bool> altKey{};
|
|
41
|
+
std::optional<bool> ctrlKey{};
|
|
42
|
+
std::optional<bool> shiftKey{};
|
|
43
|
+
std::optional<bool> metaKey{};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
inline bool operator==(const HandledKey &lhs, const HandledKey &rhs)
|
|
47
|
+
{
|
|
48
|
+
return lhs.key == rhs.key && lhs.altKey == rhs.altKey && lhs.ctrlKey == rhs.ctrlKey &&
|
|
49
|
+
lhs.shiftKey == rhs.shiftKey && lhs.metaKey == rhs.metaKey;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A key press, as reported to `onKeyDown` / `onKeyUp`. */
|
|
53
|
+
struct KeyEvent {
|
|
54
|
+
std::string key{};
|
|
55
|
+
bool altKey{false};
|
|
56
|
+
bool ctrlKey{false};
|
|
57
|
+
bool shiftKey{false};
|
|
58
|
+
bool metaKey{false};
|
|
59
|
+
bool capsLockKey{false};
|
|
60
|
+
bool numericPadKey{false};
|
|
61
|
+
bool helpKey{false};
|
|
62
|
+
bool functionKey{false};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Whether an actual press satisfies a declaration; unset modifiers match anything. */
|
|
66
|
+
inline bool operator==(const KeyEvent &lhs, const HandledKey &rhs)
|
|
67
|
+
{
|
|
68
|
+
return lhs.key == rhs.key && (!rhs.altKey.has_value() || lhs.altKey == *rhs.altKey) &&
|
|
69
|
+
(!rhs.ctrlKey.has_value() || lhs.ctrlKey == *rhs.ctrlKey) &&
|
|
70
|
+
(!rhs.shiftKey.has_value() || lhs.shiftKey == *rhs.shiftKey) &&
|
|
71
|
+
(!rhs.metaKey.has_value() || lhs.metaKey == *rhs.metaKey);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Accepts either the object form or a bare string, so `keyDownEvents={['Enter']}`
|
|
76
|
+
* works as shorthand for the common no-modifier case.
|
|
77
|
+
*/
|
|
78
|
+
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, HandledKey &result)
|
|
79
|
+
{
|
|
80
|
+
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
|
|
81
|
+
auto map = static_cast<std::unordered_map<std::string, RawValue>>(value);
|
|
82
|
+
for (const auto &pair : map) {
|
|
83
|
+
if (pair.first == "key") {
|
|
84
|
+
result.key = static_cast<std::string>(pair.second);
|
|
85
|
+
} else if (pair.first == "altKey") {
|
|
86
|
+
result.altKey = static_cast<bool>(pair.second);
|
|
87
|
+
} else if (pair.first == "ctrlKey") {
|
|
88
|
+
result.ctrlKey = static_cast<bool>(pair.second);
|
|
89
|
+
} else if (pair.first == "shiftKey") {
|
|
90
|
+
result.shiftKey = static_cast<bool>(pair.second);
|
|
91
|
+
} else if (pair.first == "metaKey") {
|
|
92
|
+
result.metaKey = static_cast<bool>(pair.second);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} else if (value.hasType<std::string>()) {
|
|
96
|
+
result.key = static_cast<std::string>(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// [macOS] Pointer and drag payloads, which only a desktop platform reports.
|
|
9
|
+
//
|
|
10
|
+
// Free-standing rather than nested in the event emitter, because
|
|
11
|
+
// microsoft/react-native-macos puts `facebook::react::MouseEvent` and
|
|
12
|
+
// `DragEvent` here and third-party Fabric components name them directly.
|
|
13
|
+
|
|
14
|
+
#pragma once
|
|
15
|
+
|
|
16
|
+
#include <react/renderer/graphics/Float.h>
|
|
17
|
+
|
|
18
|
+
#include <optional>
|
|
19
|
+
#include <string>
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
namespace facebook::react {
|
|
23
|
+
|
|
24
|
+
/** A mouse crossing, click or drag position. Fields follow the DOM MouseEvent. */
|
|
25
|
+
struct MouseEvent {
|
|
26
|
+
/** Pointer location in the target view. */
|
|
27
|
+
Float clientX{0};
|
|
28
|
+
Float clientY{0};
|
|
29
|
+
|
|
30
|
+
/** Pointer location in the window. */
|
|
31
|
+
Float screenX{0};
|
|
32
|
+
Float screenY{0};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Pointer location in the window as well. The DOM distinguishes page from
|
|
36
|
+
* screen coordinates; AppKit has no document scroll offset to make them
|
|
37
|
+
* differ, so both report the window position.
|
|
38
|
+
*/
|
|
39
|
+
Float pageX{0};
|
|
40
|
+
Float pageY{0};
|
|
41
|
+
|
|
42
|
+
bool altKey{false};
|
|
43
|
+
bool ctrlKey{false};
|
|
44
|
+
bool shiftKey{false};
|
|
45
|
+
bool metaKey{false};
|
|
46
|
+
|
|
47
|
+
/** DOM button numbering: 0 primary, 1 auxiliary, 2 secondary. */
|
|
48
|
+
int button{0};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** One dragged file, described the way the DOM File interface does. */
|
|
52
|
+
struct DataTransferFile {
|
|
53
|
+
std::string name{};
|
|
54
|
+
std::string type{};
|
|
55
|
+
|
|
56
|
+
/** A file path for a dragged file, or a data: URL for dragged image data. */
|
|
57
|
+
std::string uri{};
|
|
58
|
+
|
|
59
|
+
std::optional<int> size{};
|
|
60
|
+
std::optional<int> width{};
|
|
61
|
+
std::optional<int> height{};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
struct DataTransferItem {
|
|
65
|
+
/** "file" or "image". */
|
|
66
|
+
std::string kind{};
|
|
67
|
+
std::string type{};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** The pasteboard contents of a drag, shaped like the DOM DataTransfer. */
|
|
71
|
+
struct DataTransfer {
|
|
72
|
+
std::vector<DataTransferFile> files{};
|
|
73
|
+
std::vector<DataTransferItem> items{};
|
|
74
|
+
std::vector<std::string> types{};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
struct DragEvent : MouseEvent {
|
|
78
|
+
DataTransfer dataTransfer{};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
} // namespace facebook::react
|