From 52f025e6ded2235f391bad044540d867a1eb57b7 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 12 Feb 2024 19:59:39 +0100 Subject: [PATCH 01/23] [fabric] Add draggedTypes prop to View Summary: Add support for `draggedTypes` to the View component, enabling the configuration of a native view as a drop target. Test Plan: * Run Zeratul with Fabric enabled * Drag a file over the messages view and check that the cursor is changing indicating a drop target https://pxl.cl/4ldkW Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Subscribers: taskcreeper Differential Revision: https://phabricator.intern.facebook.com/D53674739 --- .../View/RCTViewComponentView.mm | 24 +++++++++++++++++++ .../components/view/HostPlatformViewProps.cpp | 11 ++++++++- .../components/view/HostPlatformViewProps.h | 2 ++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 086bfc1e18bdf9..a9b84016e061e6 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -588,6 +588,30 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & needsInvalidateLayer = YES; } +#if TARGET_OS_OSX // [macOS + // `draggedTypes` + if (oldViewProps.draggedTypes != newViewProps.draggedTypes) { + if (oldViewProps.draggedTypes.has_value()) { + [self unregisterDraggedTypes]; + } + + if (newViewProps.draggedTypes.has_value()) { + NSMutableArray *pasteboardTypes = [NSMutableArray new]; + for (const auto &draggedType : *newViewProps.draggedTypes) { + if (draggedType == "fileUrl") { + [pasteboardTypes addObject:NSFilenamesPboardType]; + } else if (draggedType == "image") { + [pasteboardTypes addObject:NSPasteboardTypePNG]; + [pasteboardTypes addObject:NSPasteboardTypeTIFF]; + } else if (draggedType == "string") { + [pasteboardTypes addObject:NSPasteboardTypeString]; + } + } + [self registerForDraggedTypes:pasteboardTypes]; + } + } +#endif // macOS] + _needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer; _props = std::static_pointer_cast(props); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp index 4dc677333b23f9..d28d201f634adf 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp @@ -65,7 +65,16 @@ HostPlatformViewProps::HostPlatformViewProps( rawProps, "keyUpEvents", sourceProps.keyUpEvents, - {})) {} + {})), + draggedTypes( + ReactNativeFeatureFlags::enableCppPropsIteratorSetter() + ? sourceProps.draggedTypes + : convertRawProp( + context, + rawProps, + "draggedTypes", + sourceProps.draggedTypes, + {})) {}; #define VIEW_EVENT_CASE_MACOS(eventType) \ case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \ diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.h index f2e4424f8818f4..5b7f3d8f8c0abe 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.h @@ -43,5 +43,7 @@ class HostPlatformViewProps : public BaseViewProps { std::vector keyDownEvents{}; std::vector keyUpEvents{}; + + std::vector draggedTypes{}; }; } // namespace facebook::react From 7a4f466d45a1a743da49bab58d9b14f4d72b73aa Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 12 Feb 2024 20:06:06 +0100 Subject: [PATCH 02/23] [fabric] Add drag and drop event emitters to View Summary: Add the drag and drop event emitters to View with the payload conversion matching the Paper API for dragEnter/dragLeave/drop events. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53674738 --- .../view/HostPlatformViewEventEmitter.cpp | 160 +++++++++--------- .../view/HostPlatformViewEventEmitter.h | 8 +- .../renderer/components/view/MouseEvent.h | 14 ++ 3 files changed, 101 insertions(+), 81 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp index f87f4eb37b8b34..f0b242290aa745 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp @@ -1,80 +1,80 @@ -/* - * Copyright (c) Microsoft Corporation. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - // [macOS] - -#include -#include - -namespace facebook::react { - -#pragma mark - Focus Events - -void HostPlatformViewEventEmitter::onFocus() const { - dispatchEvent("focus"); -} - -void HostPlatformViewEventEmitter::onBlur() const { - dispatchEvent("blur"); -} - -#pragma mark - Keyboard Events - -static jsi::Value keyEventPayload(jsi::Runtime& runtime, const KeyEvent& event) { - auto payload = jsi::Object(runtime); - payload.setProperty(runtime, "key", jsi::String::createFromUtf8(runtime, event.key)); - payload.setProperty(runtime, "ctrlKey", event.ctrlKey); - payload.setProperty(runtime, "shiftKey", event.shiftKey); - payload.setProperty(runtime, "altKey", event.altKey); - payload.setProperty(runtime, "metaKey", event.metaKey); - payload.setProperty(runtime, "capsLockKey", event.capsLockKey); - payload.setProperty(runtime, "numericPadKey", event.numericPadKey); - payload.setProperty(runtime, "helpKey", event.helpKey); - payload.setProperty(runtime, "functionKey", event.functionKey); - return payload; -}; - -void HostPlatformViewEventEmitter::onKeyDown(const KeyEvent& keyEvent) const { - dispatchEvent("keyDown", [keyEvent](jsi::Runtime& runtime) { - return keyEventPayload(runtime, keyEvent); - }); -} - -void HostPlatformViewEventEmitter::onKeyUp(const KeyEvent& keyEvent) const { - dispatchEvent("keyUp", [keyEvent](jsi::Runtime& runtime) { - return keyEventPayload(runtime, keyEvent); - }); -} - -#pragma mark - Mouse Events - -static jsi::Value mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { - auto payload = jsi::Object(runtime); - payload.setProperty(runtime, "clientX", event.clientX); - payload.setProperty(runtime, "clientY", event.clientY); - payload.setProperty(runtime, "screenX", event.screenX); - payload.setProperty(runtime, "screenY", event.screenY); - payload.setProperty(runtime, "altKey", event.altKey); - payload.setProperty(runtime, "ctrlKey", event.ctrlKey); - payload.setProperty(runtime, "shiftKey", event.shiftKey); - payload.setProperty(runtime, "metaKey", event.metaKey); - return payload; -}; - -void HostPlatformViewEventEmitter::onMouseEnter(const MouseEvent& mouseEvent) const { - dispatchEvent("mouseEnter", [mouseEvent](jsi::Runtime &runtime) { - return mouseEventPayload(runtime, mouseEvent); - }); -} - -void HostPlatformViewEventEmitter::onMouseLeave(const MouseEvent& mouseEvent) const { - dispatchEvent("mouseLeave", [mouseEvent](jsi::Runtime &runtime) { - return mouseEventPayload(runtime, mouseEvent); - }); -} - -} // namespace facebook::react +/* + * Copyright (c) Microsoft Corporation. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + // [macOS] + +#include +#include + +namespace facebook::react { + +#pragma mark - Focus Events + +void HostPlatformViewEventEmitter::onFocus() const { + dispatchEvent("focus"); +} + +void HostPlatformViewEventEmitter::onBlur() const { + dispatchEvent("blur"); +} + +#pragma mark - Keyboard Events + +static jsi::Value keyEventPayload(jsi::Runtime& runtime, const KeyEvent& event) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "key", jsi::String::createFromUtf8(runtime, event.key)); + payload.setProperty(runtime, "ctrlKey", event.ctrlKey); + payload.setProperty(runtime, "shiftKey", event.shiftKey); + payload.setProperty(runtime, "altKey", event.altKey); + payload.setProperty(runtime, "metaKey", event.metaKey); + payload.setProperty(runtime, "capsLockKey", event.capsLockKey); + payload.setProperty(runtime, "numericPadKey", event.numericPadKey); + payload.setProperty(runtime, "helpKey", event.helpKey); + payload.setProperty(runtime, "functionKey", event.functionKey); + return payload; +}; + +void HostPlatformViewEventEmitter::onKeyDown(const KeyEvent& keyEvent) const { + dispatchEvent("keyDown", [keyEvent](jsi::Runtime& runtime) { + return keyEventPayload(runtime, keyEvent); + }); +} + +void HostPlatformViewEventEmitter::onKeyUp(const KeyEvent& keyEvent) const { + dispatchEvent("keyUp", [keyEvent](jsi::Runtime& runtime) { + return keyEventPayload(runtime, keyEvent); + }); +} + +#pragma mark - Mouse Events + +static jsi::Value mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "clientX", event.clientX); + payload.setProperty(runtime, "clientY", event.clientY); + payload.setProperty(runtime, "screenX", event.screenX); + payload.setProperty(runtime, "screenY", event.screenY); + payload.setProperty(runtime, "altKey", event.altKey); + payload.setProperty(runtime, "ctrlKey", event.ctrlKey); + payload.setProperty(runtime, "shiftKey", event.shiftKey); + payload.setProperty(runtime, "metaKey", event.metaKey); + return payload; +}; + +void HostPlatformViewEventEmitter::onMouseEnter(const MouseEvent& mouseEvent) const { + dispatchEvent("mouseEnter", [mouseEvent](jsi::Runtime &runtime) { + return mouseEventPayload(runtime, mouseEvent); + }); +} + +void HostPlatformViewEventEmitter::onMouseLeave(const MouseEvent& mouseEvent) const { + dispatchEvent("mouseLeave", [mouseEvent](jsi::Runtime &runtime) { + return mouseEventPayload(runtime, mouseEvent); + }); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h index ff6c31a3673ad4..69d282bbf81a10 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h @@ -33,6 +33,12 @@ class HostPlatformViewEventEmitter : public BaseViewEventEmitter { void onMouseEnter(MouseEvent const& mouseEvent) const; void onMouseLeave(MouseEvent const& mouseEvent) const; + +#pragma mark - Drag and Drop Events + + void onDragEnter(DragEvent const& dragEvent) const; + void onDragLeave(DragEvent const& dragEvent) const; + void onDrop(DragEvent const& dragEvent) const; }; -} // namespace facebook::react \ No newline at end of file +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MouseEvent.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MouseEvent.h index 457083eec2fc4e..aafa96e7917ec7 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MouseEvent.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MouseEvent.h @@ -56,4 +56,18 @@ struct MouseEvent { bool metaKey{false}; }; +struct DataTransferItem { + std::string name{}; + std::string kind{}; + std::string type{}; + std::string uri{}; + std::optional size{}; + std::optional width{}; + std::optional height{}; +}; + +struct DragEvent : MouseEvent { + std::vector dataTransferItems; +}; + } // namespace facebook::react From 1055d4c21f8e9a659813015f68791ea7ca729d32 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 12 Feb 2024 20:12:18 +0100 Subject: [PATCH 03/23] [fabric] Add drag and drop support to View component Summary: Add support for drag and drop to the `ViewComponent` for files and images. The implementation converts the dropped pasteboard items the same way as in Paper. The `DataTransferItem` resulting from the pasteboard conversion of the dragged items is used to build the JS payload for the dragEnter/dragLeave/drop events. Test Plan: * Run Zeratul with Fabric enabled * Drag and drop an image on the messages view * Send the attachment https://pxl.cl/4ldB7 Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53674742 --- .../View/RCTViewComponentView.mm | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index a9b84016e061e6..d6036a86c2166a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1693,6 +1693,158 @@ - (void)keyUp:(NSEvent *)event { } +#pragma mark - Drag and Drop Events + +enum DragEventType { + DragEnter, + DragLeave, + Drop, +}; + +- (void)buildDataTransferItems:(std::vector &)dataTransferItems forPasteboard:(NSPasteboard *)pasteboard { + NSArray *fileNames = [pasteboard propertyListForType:NSFilenamesPboardType] ?: @[]; + for (NSString *file in fileNames) { + NSURL *fileURL = [NSURL fileURLWithPath:file]; + BOOL isDir = NO; + BOOL isValid = (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path isDirectory:&isDir] || isDir) ? NO : YES; + if (isValid) { + + NSString *MIMETypeString = nil; + if (fileURL.pathExtension) { + CFStringRef fileExtension = (__bridge CFStringRef)fileURL.pathExtension; + CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, NULL); + if (UTI != NULL) { + CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType); + CFRelease(UTI); + MIMETypeString = (__bridge_transfer NSString *)MIMEType; + } + } + + NSNumber *fileSizeValue = nil; + NSError *fileSizeError = nil; + BOOL success = [fileURL getResourceValue:&fileSizeValue + forKey:NSURLFileSizeKey + error:&fileSizeError]; + + NSNumber *width = nil; + NSNumber *height = nil; + if ([MIMETypeString hasPrefix:@"image/"]) { + NSImage *image = [[NSImage alloc] initWithContentsOfURL:fileURL]; + width = @(image.size.width); + height = @(image.size.height); + } + + DataTransferItem transferItem = { + .name = fileURL.lastPathComponent.UTF8String, + .kind = "file", + .type = MIMETypeString.UTF8String, + .uri = fileURL.path.UTF8String, + }; + + if (success) { + transferItem.size = fileSizeValue.intValue; + } + + if (width != nil) { + transferItem.width = width.intValue; + } + + if (height != nil) { + transferItem.height = height.intValue; + } + + dataTransferItems.push_back(transferItem); + } + } + + NSPasteboardType imageType = [pasteboard availableTypeFromArray:@[NSPasteboardTypePNG, NSPasteboardTypeTIFF]]; + if (imageType && fileNames.count == 0) { + NSString *MIMETypeString = imageType == NSPasteboardTypePNG ? @"image/png" : @"image/tiff"; + NSData *imageData = [pasteboard dataForType:imageType]; + NSImage *image = [[NSImage alloc] initWithData:imageData]; + + DataTransferItem transferItem = { + .kind = "image", + .type = MIMETypeString.UTF8String, + .uri = RCTDataURL(MIMETypeString, imageData).absoluteString.UTF8String, + .size = imageData.length, + .width = image.size.width, + .height = image.size.height, + }; + + dataTransferItems.push_back(transferItem); + } +} + +- (void)sendDragEvent:(DragEventType)eventType withLocation:(NSPoint)locationInWindow pasteboard:(NSPasteboard *)pasteboard { + if (!_eventEmitter) { + return; + } + + std::vector dataTransferItems{}; + [self buildDataTransferItems:dataTransferItems forPasteboard:pasteboard]; + + NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil]; + NSEventModifierFlags modifierFlags = self.window.currentEvent.modifierFlags; + + DragEvent dragEvent = { + { + .clientX = locationInView.x, + .clientY = locationInView.y, + .screenX = locationInWindow.x, + .screenY = locationInWindow.y, + .altKey = static_cast(modifierFlags & NSEventModifierFlagOption), + .ctrlKey = static_cast(modifierFlags & NSEventModifierFlagControl), + .shiftKey = static_cast(modifierFlags & NSEventModifierFlagShift), + .metaKey = static_cast(modifierFlags & NSEventModifierFlagCommand), + }, + .dataTransferItems = dataTransferItems, + }; + + switch (eventType) { + case DragEnter: + _eventEmitter->onDragEnter(dragEvent); + break; + + case DragLeave: + _eventEmitter->onDragLeave(dragEvent); + break; + + case Drop: + _eventEmitter->onDrop(dragEvent); + break; + } +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + NSPasteboard *pboard = sender.draggingPasteboard; + NSDragOperation sourceDragMask = sender.draggingSourceOperationMask; + + [self sendDragEvent:DragEnter withLocation:sender.draggingLocation pasteboard:pboard]; + + if ([pboard availableTypeFromArray:self.registeredDraggedTypes]) { + if (sourceDragMask & NSDragOperationLink) { + return NSDragOperationLink; + } else if (sourceDragMask & NSDragOperationCopy) { + return NSDragOperationCopy; + } + } + return NSDragOperationNone; +} + +- (void)draggingExited:(id)sender +{ + [self sendDragEvent:DragLeave withLocation:sender.draggingLocation pasteboard:sender.draggingPasteboard]; +} + +- (BOOL)performDragOperation:(id )sender +{ + [self sendDragEvent:Drop withLocation:sender.draggingLocation pasteboard:sender.draggingPasteboard]; + return YES; +} + + #pragma mark - Mouse Events - (void)emitMouseEvent { From 5aff5caeeaf683379792a6b3cbf1d282ea77cbc6 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 1 Oct 2025 14:49:08 -0700 Subject: [PATCH 04/23] no longer optional --- .../ComponentViews/View/RCTViewComponentView.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index d6036a86c2166a..ee50ecbd62947d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -591,13 +591,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & #if TARGET_OS_OSX // [macOS // `draggedTypes` if (oldViewProps.draggedTypes != newViewProps.draggedTypes) { - if (oldViewProps.draggedTypes.has_value()) { + if (!oldViewProps.draggedTypes.empty()) { [self unregisterDraggedTypes]; } - - if (newViewProps.draggedTypes.has_value()) { - NSMutableArray *pasteboardTypes = [NSMutableArray new]; - for (const auto &draggedType : *newViewProps.draggedTypes) { + + if (!newViewProps.draggedTypes.empty()) { + NSMutableArray *pasteboardTypes = [NSMutableArray arrayWithCapacity:newViewProps.draggedTypes.size()]; + for (const auto &draggedType : newViewProps.draggedTypes) { if (draggedType == "fileUrl") { [pasteboardTypes addObject:NSFilenamesPboardType]; } else if (draggedType == "image") { From 4ff4c7f3b5b6d95ce341a624be646c5457bce467 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 1 Oct 2025 14:56:47 -0700 Subject: [PATCH 05/23] take fix from meta/pristine --- .../view/HostPlatformViewEventEmitter.cpp | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp index f0b242290aa745..dfffea9c796197 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp @@ -52,7 +52,8 @@ void HostPlatformViewEventEmitter::onKeyUp(const KeyEvent& keyEvent) const { #pragma mark - Mouse Events -static jsi::Value mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { +// Returns an Object instead of value as we read and modify it in dragEventPayload. +static jsi::Object mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { auto payload = jsi::Object(runtime); payload.setProperty(runtime, "clientX", event.clientX); payload.setProperty(runtime, "clientY", event.clientY); @@ -77,4 +78,74 @@ void HostPlatformViewEventEmitter::onMouseLeave(const MouseEvent& mouseEvent) co }); } +#pragma mark - Drag and Drop Events + +static jsi::Value dataTransferPayload( + jsi::Runtime& runtime, + const std::vector& dataTransferItems) { + auto filesArray = jsi::Array(runtime, dataTransferItems.size()); + auto itemsArray = jsi::Array(runtime, dataTransferItems.size()); + auto typesArray = jsi::Array(runtime, dataTransferItems.size()); + int i = 0; + for (const auto& transferItem : dataTransferItems) { + auto fileObject = jsi::Object(runtime); + fileObject.setProperty(runtime, "name", transferItem.name); + fileObject.setProperty(runtime, "type", transferItem.type); + fileObject.setProperty(runtime, "uri", transferItem.uri); + if (transferItem.size.has_value()) { + fileObject.setProperty(runtime, "size", *transferItem.size); + } + if (transferItem.width.has_value()) { + fileObject.setProperty(runtime, "width", *transferItem.width); + } + if (transferItem.height.has_value()) { + fileObject.setProperty(runtime, "height", *transferItem.height); + } + filesArray.setValueAtIndex(runtime, i, fileObject); + + auto itemObject = jsi::Object(runtime); + itemObject.setProperty(runtime, "kind", transferItem.kind); + itemObject.setProperty(runtime, "type", transferItem.type); + itemsArray.setValueAtIndex(runtime, i, itemObject); + + typesArray.setValueAtIndex(runtime, i, transferItem.type); + i++; + } + + auto dataTransferObject = jsi::Object(runtime); + dataTransferObject.setProperty(runtime, "files", filesArray); + dataTransferObject.setProperty(runtime, "items", itemsArray); + dataTransferObject.setProperty(runtime, "types", typesArray); + + return dataTransferObject; +} + +static jsi::Value dragEventPayload( + jsi::Runtime& runtime, + const DragEvent& event) { + auto payload = mouseEventPayload(runtime, event); + auto dataTransferObject = + dataTransferPayload(runtime, event.dataTransferItems); + payload.setProperty(runtime, "dataTransfer", dataTransferObject); + return payload; +} + +void HostPlatformViewEventEmitter::onDragEnter(DragEvent const& dragEvent) const { + dispatchEvent("dragEnter", [dragEvent](jsi::Runtime &runtime) { + return dragEventPayload(runtime, dragEvent); + }); +} + +void HostPlatformViewEventEmitter::onDragLeave(DragEvent const& dragEvent) const { + dispatchEvent("dragLeave", [dragEvent](jsi::Runtime &runtime) { + return dragEventPayload(runtime, dragEvent); + }); +} + +void HostPlatformViewEventEmitter::onDrop(DragEvent const& dragEvent) const { + dispatchEvent("drop", [dragEvent](jsi::Runtime &runtime) { + return dragEventPayload(runtime, dragEvent); + }); +} + } // namespace facebook::react From 4ac7e98cf19d443b661c8c04ea17083537c7be3d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 1 Oct 2025 15:00:23 -0700 Subject: [PATCH 06/23] Add missing header --- .../Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index ee50ecbd62947d..0cec0d8ac9f649 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -32,6 +32,7 @@ #if TARGET_OS_OSX // [macOS #import #import +#import #endif // macOS] using namespace facebook::react; From e1ec4de4b1a41e96296a43561278146e840e254d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 1 Oct 2025 15:18:53 -0700 Subject: [PATCH 07/23] Update PressableExample --- packages/rn-tester/js/examples/Pressable/PressableExample.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 4a25c165847b19..3c853ef1e12e4b 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -127,6 +127,10 @@ function PressableFeedbackEvents() { onHoverOut={() => appendEvent('hoverOut')} onFocus={() => appendEvent('focus')} onBlur={() => appendEvent('blur')} + onDragEnter={() => appendEvent('dragEnter')} + onDragLeave={() => appendEvent('dragLeave')} + onDrop={() => appendEvent('drop')} + draggedTypes={'fileUrl'} // macOS] onPress={() => appendEvent('press')} onPressIn={() => appendEvent('pressIn')} From f8e51b4323e455265aa84e434df90c453c1e58b9 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 1 Oct 2025 15:27:06 -0700 Subject: [PATCH 08/23] undo event emitter --- .../view/HostPlatformViewEventEmitter.cpp | 231 ++++++------------ 1 file changed, 80 insertions(+), 151 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp index dfffea9c796197..f87f4eb37b8b34 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp @@ -1,151 +1,80 @@ -/* - * Copyright (c) Microsoft Corporation. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - // [macOS] - -#include -#include - -namespace facebook::react { - -#pragma mark - Focus Events - -void HostPlatformViewEventEmitter::onFocus() const { - dispatchEvent("focus"); -} - -void HostPlatformViewEventEmitter::onBlur() const { - dispatchEvent("blur"); -} - -#pragma mark - Keyboard Events - -static jsi::Value keyEventPayload(jsi::Runtime& runtime, const KeyEvent& event) { - auto payload = jsi::Object(runtime); - payload.setProperty(runtime, "key", jsi::String::createFromUtf8(runtime, event.key)); - payload.setProperty(runtime, "ctrlKey", event.ctrlKey); - payload.setProperty(runtime, "shiftKey", event.shiftKey); - payload.setProperty(runtime, "altKey", event.altKey); - payload.setProperty(runtime, "metaKey", event.metaKey); - payload.setProperty(runtime, "capsLockKey", event.capsLockKey); - payload.setProperty(runtime, "numericPadKey", event.numericPadKey); - payload.setProperty(runtime, "helpKey", event.helpKey); - payload.setProperty(runtime, "functionKey", event.functionKey); - return payload; -}; - -void HostPlatformViewEventEmitter::onKeyDown(const KeyEvent& keyEvent) const { - dispatchEvent("keyDown", [keyEvent](jsi::Runtime& runtime) { - return keyEventPayload(runtime, keyEvent); - }); -} - -void HostPlatformViewEventEmitter::onKeyUp(const KeyEvent& keyEvent) const { - dispatchEvent("keyUp", [keyEvent](jsi::Runtime& runtime) { - return keyEventPayload(runtime, keyEvent); - }); -} - -#pragma mark - Mouse Events - -// Returns an Object instead of value as we read and modify it in dragEventPayload. -static jsi::Object mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { - auto payload = jsi::Object(runtime); - payload.setProperty(runtime, "clientX", event.clientX); - payload.setProperty(runtime, "clientY", event.clientY); - payload.setProperty(runtime, "screenX", event.screenX); - payload.setProperty(runtime, "screenY", event.screenY); - payload.setProperty(runtime, "altKey", event.altKey); - payload.setProperty(runtime, "ctrlKey", event.ctrlKey); - payload.setProperty(runtime, "shiftKey", event.shiftKey); - payload.setProperty(runtime, "metaKey", event.metaKey); - return payload; -}; - -void HostPlatformViewEventEmitter::onMouseEnter(const MouseEvent& mouseEvent) const { - dispatchEvent("mouseEnter", [mouseEvent](jsi::Runtime &runtime) { - return mouseEventPayload(runtime, mouseEvent); - }); -} - -void HostPlatformViewEventEmitter::onMouseLeave(const MouseEvent& mouseEvent) const { - dispatchEvent("mouseLeave", [mouseEvent](jsi::Runtime &runtime) { - return mouseEventPayload(runtime, mouseEvent); - }); -} - -#pragma mark - Drag and Drop Events - -static jsi::Value dataTransferPayload( - jsi::Runtime& runtime, - const std::vector& dataTransferItems) { - auto filesArray = jsi::Array(runtime, dataTransferItems.size()); - auto itemsArray = jsi::Array(runtime, dataTransferItems.size()); - auto typesArray = jsi::Array(runtime, dataTransferItems.size()); - int i = 0; - for (const auto& transferItem : dataTransferItems) { - auto fileObject = jsi::Object(runtime); - fileObject.setProperty(runtime, "name", transferItem.name); - fileObject.setProperty(runtime, "type", transferItem.type); - fileObject.setProperty(runtime, "uri", transferItem.uri); - if (transferItem.size.has_value()) { - fileObject.setProperty(runtime, "size", *transferItem.size); - } - if (transferItem.width.has_value()) { - fileObject.setProperty(runtime, "width", *transferItem.width); - } - if (transferItem.height.has_value()) { - fileObject.setProperty(runtime, "height", *transferItem.height); - } - filesArray.setValueAtIndex(runtime, i, fileObject); - - auto itemObject = jsi::Object(runtime); - itemObject.setProperty(runtime, "kind", transferItem.kind); - itemObject.setProperty(runtime, "type", transferItem.type); - itemsArray.setValueAtIndex(runtime, i, itemObject); - - typesArray.setValueAtIndex(runtime, i, transferItem.type); - i++; - } - - auto dataTransferObject = jsi::Object(runtime); - dataTransferObject.setProperty(runtime, "files", filesArray); - dataTransferObject.setProperty(runtime, "items", itemsArray); - dataTransferObject.setProperty(runtime, "types", typesArray); - - return dataTransferObject; -} - -static jsi::Value dragEventPayload( - jsi::Runtime& runtime, - const DragEvent& event) { - auto payload = mouseEventPayload(runtime, event); - auto dataTransferObject = - dataTransferPayload(runtime, event.dataTransferItems); - payload.setProperty(runtime, "dataTransfer", dataTransferObject); - return payload; -} - -void HostPlatformViewEventEmitter::onDragEnter(DragEvent const& dragEvent) const { - dispatchEvent("dragEnter", [dragEvent](jsi::Runtime &runtime) { - return dragEventPayload(runtime, dragEvent); - }); -} - -void HostPlatformViewEventEmitter::onDragLeave(DragEvent const& dragEvent) const { - dispatchEvent("dragLeave", [dragEvent](jsi::Runtime &runtime) { - return dragEventPayload(runtime, dragEvent); - }); -} - -void HostPlatformViewEventEmitter::onDrop(DragEvent const& dragEvent) const { - dispatchEvent("drop", [dragEvent](jsi::Runtime &runtime) { - return dragEventPayload(runtime, dragEvent); - }); -} - -} // namespace facebook::react +/* + * Copyright (c) Microsoft Corporation. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + // [macOS] + +#include +#include + +namespace facebook::react { + +#pragma mark - Focus Events + +void HostPlatformViewEventEmitter::onFocus() const { + dispatchEvent("focus"); +} + +void HostPlatformViewEventEmitter::onBlur() const { + dispatchEvent("blur"); +} + +#pragma mark - Keyboard Events + +static jsi::Value keyEventPayload(jsi::Runtime& runtime, const KeyEvent& event) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "key", jsi::String::createFromUtf8(runtime, event.key)); + payload.setProperty(runtime, "ctrlKey", event.ctrlKey); + payload.setProperty(runtime, "shiftKey", event.shiftKey); + payload.setProperty(runtime, "altKey", event.altKey); + payload.setProperty(runtime, "metaKey", event.metaKey); + payload.setProperty(runtime, "capsLockKey", event.capsLockKey); + payload.setProperty(runtime, "numericPadKey", event.numericPadKey); + payload.setProperty(runtime, "helpKey", event.helpKey); + payload.setProperty(runtime, "functionKey", event.functionKey); + return payload; +}; + +void HostPlatformViewEventEmitter::onKeyDown(const KeyEvent& keyEvent) const { + dispatchEvent("keyDown", [keyEvent](jsi::Runtime& runtime) { + return keyEventPayload(runtime, keyEvent); + }); +} + +void HostPlatformViewEventEmitter::onKeyUp(const KeyEvent& keyEvent) const { + dispatchEvent("keyUp", [keyEvent](jsi::Runtime& runtime) { + return keyEventPayload(runtime, keyEvent); + }); +} + +#pragma mark - Mouse Events + +static jsi::Value mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "clientX", event.clientX); + payload.setProperty(runtime, "clientY", event.clientY); + payload.setProperty(runtime, "screenX", event.screenX); + payload.setProperty(runtime, "screenY", event.screenY); + payload.setProperty(runtime, "altKey", event.altKey); + payload.setProperty(runtime, "ctrlKey", event.ctrlKey); + payload.setProperty(runtime, "shiftKey", event.shiftKey); + payload.setProperty(runtime, "metaKey", event.metaKey); + return payload; +}; + +void HostPlatformViewEventEmitter::onMouseEnter(const MouseEvent& mouseEvent) const { + dispatchEvent("mouseEnter", [mouseEvent](jsi::Runtime &runtime) { + return mouseEventPayload(runtime, mouseEvent); + }); +} + +void HostPlatformViewEventEmitter::onMouseLeave(const MouseEvent& mouseEvent) const { + dispatchEvent("mouseLeave", [mouseEvent](jsi::Runtime &runtime) { + return mouseEventPayload(runtime, mouseEvent); + }); +} + +} // namespace facebook::react From ebc4aea3a81703026e95186522c2080dfb3dc566 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 1 Oct 2025 15:27:26 -0700 Subject: [PATCH 09/23] redo event emitter --- .../view/HostPlatformViewEventEmitter.cpp | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp index f87f4eb37b8b34..0be170a1dafbae 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp @@ -52,7 +52,8 @@ void HostPlatformViewEventEmitter::onKeyUp(const KeyEvent& keyEvent) const { #pragma mark - Mouse Events -static jsi::Value mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { +// Returns an Object instead of value as we read and modify it in dragEventPayload. +static jsi::Object mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& event) { auto payload = jsi::Object(runtime); payload.setProperty(runtime, "clientX", event.clientX); payload.setProperty(runtime, "clientY", event.clientY); @@ -77,4 +78,74 @@ void HostPlatformViewEventEmitter::onMouseLeave(const MouseEvent& mouseEvent) co }); } +#pragma mark - Drag and Drop Events + +static jsi::Value dataTransferPayload( + jsi::Runtime& runtime, + const std::vector& dataTransferItems) { + auto filesArray = jsi::Array(runtime, dataTransferItems.size()); + auto itemsArray = jsi::Array(runtime, dataTransferItems.size()); + auto typesArray = jsi::Array(runtime, dataTransferItems.size()); + int i = 0; + for (const auto& transferItem : dataTransferItems) { + auto fileObject = jsi::Object(runtime); + fileObject.setProperty(runtime, "name", transferItem.name); + fileObject.setProperty(runtime, "type", transferItem.type); + fileObject.setProperty(runtime, "uri", transferItem.uri); + if (transferItem.size.has_value()) { + fileObject.setProperty(runtime, "size", *transferItem.size); + } + if (transferItem.width.has_value()) { + fileObject.setProperty(runtime, "width", *transferItem.width); + } + if (transferItem.height.has_value()) { + fileObject.setProperty(runtime, "height", *transferItem.height); + } + filesArray.setValueAtIndex(runtime, i, fileObject); + + auto itemObject = jsi::Object(runtime); + itemObject.setProperty(runtime, "kind", transferItem.kind); + itemObject.setProperty(runtime, "type", transferItem.type); + itemsArray.setValueAtIndex(runtime, i, itemObject); + + typesArray.setValueAtIndex(runtime, i, transferItem.type); + i++; + } + + auto dataTransferObject = jsi::Object(runtime); + dataTransferObject.setProperty(runtime, "files", filesArray); + dataTransferObject.setProperty(runtime, "items", itemsArray); + dataTransferObject.setProperty(runtime, "types", typesArray); + + return dataTransferObject; +} + +static jsi::Value dragEventPayload( + jsi::Runtime& runtime, + const DragEvent& event) { + auto payload = mouseEventPayload(runtime, event); + auto dataTransferObject = + dataTransferPayload(runtime, event.dataTransferItems); + payload.setProperty(runtime, "dataTransfer", dataTransferObject); + return payload; +} + +void HostPlatformViewEventEmitter::onDragEnter(DragEvent const& dragEvent) const { + dispatchEvent("dragEnter", [dragEvent](jsi::Runtime &runtime) { + return dragEventPayload(runtime, dragEvent); + }); +} + +void HostPlatformViewEventEmitter::onDragLeave(DragEvent const& dragEvent) const { + dispatchEvent("dragLeave", [dragEvent](jsi::Runtime &runtime) { + return dragEventPayload(runtime, dragEvent); + }); +} + +void HostPlatformViewEventEmitter::onDrop(DragEvent const& dragEvent) const { + dispatchEvent("drop", [dragEvent](jsi::Runtime &runtime) { + return dragEventPayload(runtime, dragEvent); + }); +} + } // namespace facebook::react From 2851aacc8d140de9983862b81a916b61f8641cb7 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 1 Oct 2025 16:08:50 -0700 Subject: [PATCH 10/23] Update HostPlatformViewProps.cpp --- .../react/renderer/components/view/HostPlatformViewProps.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp index d28d201f634adf..1b8cd42746337f 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp @@ -111,6 +111,7 @@ void HostPlatformViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing); RAW_SET_PROP_SWITCH_CASE_BASIC(keyDownEvents); RAW_SET_PROP_SWITCH_CASE_BASIC(keyUpEvents); + RAW_SET_PROP_SWITCH_CASE_BASIC(draggedTypes); } } From 9a6095c35d280297f4893fe8bb10cb709aea2092 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Thu, 2 Oct 2025 13:47:25 -0700 Subject: [PATCH 11/23] Apply suggestions from code review Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> --- .../View/RCTViewComponentView.mm | 26 ++++++------------- .../components/view/HostPlatformViewProps.cpp | 10 +++---- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 0cec0d8ac9f649..c7954525ee2daa 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1707,7 +1707,7 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem for (NSString *file in fileNames) { NSURL *fileURL = [NSURL fileURLWithPath:file]; BOOL isDir = NO; - BOOL isValid = (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path isDirectory:&isDir] || isDir) ? NO : YES; + BOOL isValid = [[NSFileManager defaultManager] fileExistsAtPath:fileURL.path isDirectory:&isDir] && !isDir; if (isValid) { NSString *MIMETypeString = nil; @@ -1727,14 +1727,6 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem forKey:NSURLFileSizeKey error:&fileSizeError]; - NSNumber *width = nil; - NSNumber *height = nil; - if ([MIMETypeString hasPrefix:@"image/"]) { - NSImage *image = [[NSImage alloc] initWithContentsOfURL:fileURL]; - width = @(image.size.width); - height = @(image.size.height); - } - DataTransferItem transferItem = { .name = fileURL.lastPathComponent.UTF8String, .kind = "file", @@ -1746,12 +1738,10 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem transferItem.size = fileSizeValue.intValue; } - if (width != nil) { - transferItem.width = width.intValue; - } - - if (height != nil) { - transferItem.height = height.intValue; + if ([MIMETypeString hasPrefix:@"image/"]) { + NSImage *image = [[NSImage alloc] initWithContentsOfURL:fileURL]; + transferItem.width = static_cast(image.size.width); + transferItem.height = static_cast(image.size.height); } dataTransferItems.push_back(transferItem); @@ -1768,9 +1758,9 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem .kind = "image", .type = MIMETypeString.UTF8String, .uri = RCTDataURL(MIMETypeString, imageData).absoluteString.UTF8String, - .size = imageData.length, - .width = image.size.width, - .height = image.size.height, + .size = static_cast(imageData.length), + .width = static_cast(image.size.width), + .height = static_cast(image.size.height), }; dataTransferItems.push_back(transferItem); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp index 1b8cd42746337f..7154d9d9068d67 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp @@ -70,11 +70,11 @@ HostPlatformViewProps::HostPlatformViewProps( ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.draggedTypes : convertRawProp( - context, - rawProps, - "draggedTypes", - sourceProps.draggedTypes, - {})) {}; + context, + rawProps, + "draggedTypes", + sourceProps.draggedTypes, + {})) {}; #define VIEW_EVENT_CASE_MACOS(eventType) \ case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \ From 42dc7693c5863a07a68f630023b3f623978cade0 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 15:01:17 -0700 Subject: [PATCH 12/23] Add a new drag and drop example --- .../DragAndDrop/DragAndDropExample.js | 214 ++++++++++++++++++ .../TextInput/TextInputExample.ios.js | 108 --------- .../rn-tester/js/utils/RNTesterList.ios.js | 5 + 3 files changed, 219 insertions(+), 108 deletions(-) create mode 100644 packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js diff --git a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js new file mode 100644 index 00000000000000..1562613fc84ef1 --- /dev/null +++ b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js @@ -0,0 +1,214 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +// [macOS + +import type {PasteEvent} from 'react-native/Libraries/Components/TextInput/TextInput'; + +import ExampleTextInput from '../TextInput/ExampleTextInput'; +import React from 'react'; +import {Image, StyleSheet, Text, View} from 'react-native'; + +const styles = StyleSheet.create({ + multiline: { + height: 50, + }, +}); + +function OnDragEnterOnDragLeaveOnDrop(): React.Node { + // $FlowFixMe[missing-empty-array-annot] + const [log, setLog] = React.useState([]); + const appendLog = (line: string) => { + const limit = 6; + let newLog = log.slice(0, limit - 1); + newLog.unshift(line); + setLog(newLog); + }; + return ( + <> + appendLog('SinglelineEnter')} + onDragLeave={e => appendLog('SinglelineLeave')} + onDrop={e => appendLog('SinglelineDrop')} + style={styles.multiline} + placeholder="SINGLE LINE with onDragEnter|Leave() and onDrop()" + /> + appendLog('MultilineEnter')} + onDragLeave={e => appendLog('MultilineLeave')} + onDrop={e => appendLog('MultilineDrop')} + style={styles.multiline} + placeholder="MULTI LINE with onDragEnter|Leave() and onDrop()" + /> + {log.join('\n')} + + + + ); +} + +function OnPaste(): React.Node { + // $FlowFixMe[missing-empty-array-annot] + const [log, setLog] = React.useState([]); + const appendLog = (line: string) => { + const limit = 3; + let newLog = log.slice(0, limit - 1); + newLog.unshift(line); + setLog(newLog); + }; + const [imageUri, setImageUri] = React.useState( + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==', + ); + return ( + <> + { + appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types)); + setImageUri(e.nativeEvent.dataTransfer.files[0].uri); + }} + pastedTypes={['string']} + placeholder="MULTI LINE with onPaste() text from clipboard" + /> + { + appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types)); + setImageUri(e.nativeEvent.dataTransfer.files[0].uri); + }} + pastedTypes={['fileUrl', 'image', 'string']} + placeholder="MULTI LINE with onPaste() for PNG/TIFF images from clipboard or fileUrl (via Finder) and text from clipboard" + /> + {log.join('\n')} + + + ); +} + +function OnDropView(): React.Node { + // $FlowFixMe[missing-empty-array-annot] + const [log, setLog] = React.useState([]); + const appendLog = (line: string) => { + const limit = 3; + let newLog = log.slice(0, limit - 1); + newLog.unshift(line); + setLog(newLog); + }; + const [imageUri, setImageUri] = React.useState( + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==', + ); + const [isDraggingOver, setIsDraggingOver] = React.useState(false); + + return ( + <> + { + appendLog('onDragEnter'); + setIsDraggingOver(true); + }} + onDragLeave={e => { + appendLog('onDragLeave'); + setIsDraggingOver(false); + }} + onDrop={e => { + appendLog('onDrop'); + setIsDraggingOver(false); + if (e.nativeEvent.dataTransfer.files && e.nativeEvent.dataTransfer.files[0]) { + setImageUri(e.nativeEvent.dataTransfer.files[0].uri); + } + }} + style={{ + height: 150, + backgroundColor: isDraggingOver ? '#e3f2fd' : '#f0f0f0', + borderWidth: 2, + borderColor: isDraggingOver ? '#2196f3' : '#0066cc', + borderStyle: 'dashed', + borderRadius: 8, + justifyContent: 'center', + alignItems: 'center', + marginVertical: 10, + }}> + + {isDraggingOver ? 'Release to drop' : 'Drop an image or file here'} + + + + + Event Log: + {log.join('\n')} + + + Dropped Image: + + + + + ); +} + +exports.title = 'Drag and Drop Events'; +exports.category = 'UI'; +exports.description = 'Demonstrates onDragEnter, onDragLeave, onDrop, and onPaste event handling in TextInput.'; +exports.examples = [ + { + title: 'onDrop with View - Drop Image', + render: function (): React.Node { + return ; + }, + }, + { + title: 'onDragEnter, onDragLeave and onDrop - Single- & MultiLineTextInput', + render: function (): React.Node { + return ; + }, + }, + { + title: 'onPaste - MultiLineTextInput', + render: function (): React.Node { + return ; + }, + }, +]; + +// macOS] diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index 909d870acb0797..fa2d030909dc18 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -17,7 +17,6 @@ import type { import type {KeyboardTypeOptions} from 'react-native/Libraries/Components/TextInput/TextInput'; // [macOS import type { - PasteEvent, SettingChangeEvent, } from 'react-native/Libraries/Components/TextInput/TextInput'; // macOS] @@ -29,7 +28,6 @@ import { Alert, Button, InputAccessoryView, - Image, // [macOS] Platform, // [macOS] StyleSheet, Switch, @@ -388,99 +386,6 @@ function SpellingAndGrammarEvents(): React.Node { ); } - -function OnDragEnterOnDragLeaveOnDrop(): React.Node { - // $FlowFixMe[missing-empty-array-annot] - const [log, setLog] = React.useState([]); - const appendLog = (line: string) => { - const limit = 6; - let newLog = log.slice(0, limit - 1); - newLog.unshift(line); - setLog(newLog); - }; - return ( - <> - appendLog('SinglelineEnter')} - onDragLeave={e => appendLog('SinglelineLeave')} - onDrop={e => appendLog('SinglelineDrop')} - style={styles.multiline} - placeholder="SINGLE LINE with onDragEnter|Leave() and onDrop()" - /> - appendLog('MultilineEnter')} - onDragLeave={e => appendLog('MultilineLeave')} - onDrop={e => appendLog('MultilineDrop')} - style={styles.multiline} - placeholder="MULTI LINE with onDragEnter|Leave() and onDrop()" - /> - {log.join('\n')} - - - - ); -} - -function OnPaste(): React.Node { - // $FlowFixMe[missing-empty-array-annot] - const [log, setLog] = React.useState([]); - const appendLog = (line: string) => { - const limit = 3; - let newLog = log.slice(0, limit - 1); - newLog.unshift(line); - setLog(newLog); - }; - const [imageUri, setImageUri] = React.useState( - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==', - ); - return ( - <> - { - appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types)); - setImageUri(e.nativeEvent.dataTransfer.files[0].uri); - }} - pastedTypes={['string']} - placeholder="MULTI LINE with onPaste() text from clipboard" - /> - { - appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types)); - setImageUri(e.nativeEvent.dataTransfer.files[0].uri); - }} - pastedTypes={['fileUrl', 'image', 'string']} - placeholder="MULTI LINE with onPaste() for PNG/TIFF images from clipboard or fileUrl (via Finder) and text from clipboard" - /> - {log.join('\n')} - - - ); -} // macOS] const textInputExamples: Array = [ @@ -1233,19 +1138,6 @@ if (Platform.OS === 'macos') { ); }, }, - { - title: - 'onDragEnter, onDragLeave and onDrop - Single- & MultiLineTextInput', - render: function (): React.Node { - return ; - }, - }, - { - title: 'onPaste - MultiLineTextInput', - render: function (): React.Node { - return ; - }, - }, { title: 'Cursor color', render: function (): React.Node { diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js index 6d7ee8ce1d7185..9541a04f4c76e9 100644 --- a/packages/rn-tester/js/utils/RNTesterList.ios.js +++ b/packages/rn-tester/js/utils/RNTesterList.ios.js @@ -252,6 +252,11 @@ const APIs: Array = ([ module: require('../examples/DisplayContents/DisplayContentsExample') .default, }, + { + key: 'DragAndDropExample', + category: 'UI', + module: require('../examples/DragAndDrop/DragAndDropExample'), + }, // Only show the link for the example if the API is available. // $FlowExpectedError[cannot-resolve-name] typeof IntersectionObserver === 'function' From 3f45e989e922ab3ae2c73b34c8b068dbe9609d3f Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 15:09:50 -0700 Subject: [PATCH 13/23] Move example to top --- .../DragAndDrop/DragAndDropExample.js | 149 +++++++++--------- 1 file changed, 75 insertions(+), 74 deletions(-) diff --git a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js index 1562613fc84ef1..62eee32c9dc848 100644 --- a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js +++ b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js @@ -24,6 +24,77 @@ const styles = StyleSheet.create({ }, }); + +function DragDropView(): React.Node { + // $FlowFixMe[missing-empty-array-annot] + const [log, setLog] = React.useState([]); + const appendLog = (line: string) => { + const limit = 3; + let newLog = log.slice(0, limit - 1); + newLog.unshift(line); + setLog(newLog); + }; + const [imageUri, setImageUri] = React.useState( + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==', + ); + const [isDraggingOver, setIsDraggingOver] = React.useState(false); + + return ( + <> + { + appendLog('onDragEnter'); + setIsDraggingOver(true); + }} + onDragLeave={e => { + appendLog('onDragLeave'); + setIsDraggingOver(false); + }} + onDrop={e => { + appendLog('onDrop'); + setIsDraggingOver(false); + if (e.nativeEvent.dataTransfer.files && e.nativeEvent.dataTransfer.files[0]) { + setImageUri(e.nativeEvent.dataTransfer.files[0].uri); + } + }} + style={{ + height: 150, + backgroundColor: isDraggingOver ? '#e3f2fd' : '#f0f0f0', + borderWidth: 2, + borderColor: isDraggingOver ? '#2196f3' : '#0066cc', + borderStyle: 'dashed', + borderRadius: 8, + justifyContent: 'center', + alignItems: 'center', + marginVertical: 10, + }}> + + {isDraggingOver ? 'Release to drop' : 'Drop an image or file here'} + + + + + Event Log: + {log.join('\n')} + + + Dropped Image: + + + + + ); +} + function OnDragEnterOnDragLeaveOnDrop(): React.Node { // $FlowFixMe[missing-empty-array-annot] const [log, setLog] = React.useState([]); @@ -117,94 +188,24 @@ function OnPaste(): React.Node { ); } -function OnDropView(): React.Node { - // $FlowFixMe[missing-empty-array-annot] - const [log, setLog] = React.useState([]); - const appendLog = (line: string) => { - const limit = 3; - let newLog = log.slice(0, limit - 1); - newLog.unshift(line); - setLog(newLog); - }; - const [imageUri, setImageUri] = React.useState( - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==', - ); - const [isDraggingOver, setIsDraggingOver] = React.useState(false); - - return ( - <> - { - appendLog('onDragEnter'); - setIsDraggingOver(true); - }} - onDragLeave={e => { - appendLog('onDragLeave'); - setIsDraggingOver(false); - }} - onDrop={e => { - appendLog('onDrop'); - setIsDraggingOver(false); - if (e.nativeEvent.dataTransfer.files && e.nativeEvent.dataTransfer.files[0]) { - setImageUri(e.nativeEvent.dataTransfer.files[0].uri); - } - }} - style={{ - height: 150, - backgroundColor: isDraggingOver ? '#e3f2fd' : '#f0f0f0', - borderWidth: 2, - borderColor: isDraggingOver ? '#2196f3' : '#0066cc', - borderStyle: 'dashed', - borderRadius: 8, - justifyContent: 'center', - alignItems: 'center', - marginVertical: 10, - }}> - - {isDraggingOver ? 'Release to drop' : 'Drop an image or file here'} - - - - - Event Log: - {log.join('\n')} - - - Dropped Image: - - - - - ); -} - exports.title = 'Drag and Drop Events'; exports.category = 'UI'; exports.description = 'Demonstrates onDragEnter, onDragLeave, onDrop, and onPaste event handling in TextInput.'; exports.examples = [ { - title: 'onDrop with View - Drop Image', + title: 'Drag and Drop (View)', render: function (): React.Node { - return ; + return ; }, }, { - title: 'onDragEnter, onDragLeave and onDrop - Single- & MultiLineTextInput', + title: 'Drag and Drop (TextInput)', render: function (): React.Node { return ; }, }, { - title: 'onPaste - MultiLineTextInput', + title: 'onPaste (MultiLineTextInput)', render: function (): React.Node { return ; }, From a6452f894ceebead418d449aea9babad9c00508b Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 16:18:37 -0700 Subject: [PATCH 14/23] sendDragEvent -> emitDragEvent --- .../ComponentViews/View/RCTViewComponentView.mm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index c7954525ee2daa..e57cfe8cc18e62 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1767,11 +1767,14 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem } } -- (void)sendDragEvent:(DragEventType)eventType withLocation:(NSPoint)locationInWindow pasteboard:(NSPasteboard *)pasteboard { +- (void)emitDragEvent:(DragEventType)eventType draggingInfo:(id)sender { if (!_eventEmitter) { return; } + NSPoint locationInWindow = sender.draggingLocation; + NSPasteboard *pasteboard = sender.draggingPasteboard; + std::vector dataTransferItems{}; [self buildDataTransferItems:dataTransferItems forPasteboard:pasteboard]; @@ -1812,7 +1815,7 @@ - (NSDragOperation)draggingEntered:(id )sender NSPasteboard *pboard = sender.draggingPasteboard; NSDragOperation sourceDragMask = sender.draggingSourceOperationMask; - [self sendDragEvent:DragEnter withLocation:sender.draggingLocation pasteboard:pboard]; + [self emitDragEvent:DragEnter draggingInfo:sender]; if ([pboard availableTypeFromArray:self.registeredDraggedTypes]) { if (sourceDragMask & NSDragOperationLink) { @@ -1826,12 +1829,12 @@ - (NSDragOperation)draggingEntered:(id )sender - (void)draggingExited:(id)sender { - [self sendDragEvent:DragLeave withLocation:sender.draggingLocation pasteboard:sender.draggingPasteboard]; + [self emitDragEvent:DragLeave draggingInfo:sender]; } - (BOOL)performDragOperation:(id )sender { - [self sendDragEvent:Drop withLocation:sender.draggingLocation pasteboard:sender.draggingPasteboard]; + [self emitDragEvent:Drop draggingInfo:sender]; return YES; } From e10f5caf5a8c19731d2ef7c85fca5e20edd05664 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 16:20:29 -0700 Subject: [PATCH 15/23] nil check strings --- .../ComponentViews/View/RCTViewComponentView.mm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index e57cfe8cc18e62..7bb8d77ef3a965 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1728,10 +1728,10 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem error:&fileSizeError]; DataTransferItem transferItem = { - .name = fileURL.lastPathComponent.UTF8String, + .name = fileURL.lastPathComponent ? fileURL.lastPathComponent.UTF8String : "", .kind = "file", - .type = MIMETypeString.UTF8String, - .uri = fileURL.path.UTF8String, + .type = MIMETypeString ? MIMETypeString.UTF8String : "", + .uri = fileURL.path ? fileURL.path.UTF8String : "", }; if (success) { @@ -1754,10 +1754,12 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem NSData *imageData = [pasteboard dataForType:imageType]; NSImage *image = [[NSImage alloc] initWithData:imageData]; + NSString *dataURLString = RCTDataURL(MIMETypeString, imageData).absoluteString; + DataTransferItem transferItem = { .kind = "image", - .type = MIMETypeString.UTF8String, - .uri = RCTDataURL(MIMETypeString, imageData).absoluteString.UTF8String, + .type = MIMETypeString ? MIMETypeString.UTF8String : "", + .uri = dataURLString ? dataURLString.UTF8String : "", .size = static_cast(imageData.length), .width = static_cast(image.size.width), .height = static_cast(image.size.height), From 0f79940984ee41f17fc3f21b96d9b994e24429b3 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 16:37:58 -0700 Subject: [PATCH 16/23] Use cgImage --- .../ComponentViews/View/RCTViewComponentView.mm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 7bb8d77ef3a965..53fb1407114df3 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1740,8 +1740,9 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem if ([MIMETypeString hasPrefix:@"image/"]) { NSImage *image = [[NSImage alloc] initWithContentsOfURL:fileURL]; - transferItem.width = static_cast(image.size.width); - transferItem.height = static_cast(image.size.height); + CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil]; + transferItem.width = static_cast(CGImageGetWidth(cgImage)); + transferItem.height = static_cast(CGImageGetHeight(cgImage)); } dataTransferItems.push_back(transferItem); @@ -1753,6 +1754,7 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem NSString *MIMETypeString = imageType == NSPasteboardTypePNG ? @"image/png" : @"image/tiff"; NSData *imageData = [pasteboard dataForType:imageType]; NSImage *image = [[NSImage alloc] initWithData:imageData]; + CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil]; NSString *dataURLString = RCTDataURL(MIMETypeString, imageData).absoluteString; @@ -1761,8 +1763,8 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem .type = MIMETypeString ? MIMETypeString.UTF8String : "", .uri = dataURLString ? dataURLString.UTF8String : "", .size = static_cast(imageData.length), - .width = static_cast(image.size.width), - .height = static_cast(image.size.height), + .width = static_cast(CGImageGetWidth(cgImage)), + .height = static_cast(CGImageGetHeight(cgImage)), }; dataTransferItems.push_back(transferItem); From c3afaf04ea8c56c963f57bb557568d5d66d813be Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 16:38:06 -0700 Subject: [PATCH 17/23] update js example --- .../js/examples/DragAndDrop/DragAndDropExample.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js index 62eee32c9dc848..22338568921752 100644 --- a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js +++ b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js @@ -29,7 +29,7 @@ function DragDropView(): React.Node { // $FlowFixMe[missing-empty-array-annot] const [log, setLog] = React.useState([]); const appendLog = (line: string) => { - const limit = 3; + const limit = 6; let newLog = log.slice(0, limit - 1); newLog.unshift(line); setLog(newLog); @@ -55,7 +55,13 @@ function DragDropView(): React.Node { appendLog('onDrop'); setIsDraggingOver(false); if (e.nativeEvent.dataTransfer.files && e.nativeEvent.dataTransfer.files[0]) { - setImageUri(e.nativeEvent.dataTransfer.files[0].uri); + const file = e.nativeEvent.dataTransfer.files[0]; + if (file.type.startsWith('image/')) { + appendLog('Dropped image file: ' + file.name); + } else { + appendLog('Dropped file: ' + file.name); + } + setImageUri(file.uri); } }} style={{ From d8ab0f4783e5b704401dedca768a37405b938ff3 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 17:47:32 -0700 Subject: [PATCH 18/23] Update types --- .../Components/Pressable/Pressable.js | 7 ++- .../Touchable/TouchableWithoutFeedback.js | 10 ++-- .../Libraries/Components/View/DraggedType.js | 4 +- .../Components/View/ViewPropTypes.d.ts | 9 +-- .../Components/View/ViewPropTypes.js | 7 ++- .../Libraries/Types/CoreEventTypes.d.ts | 22 ++++++++ .../Libraries/Types/CoreEventTypes.js | 56 +++++++++++++++++++ .../__snapshots__/public-api-test.js.snap | 49 ++++++++++++---- 8 files changed, 137 insertions(+), 27 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 9e13dcef486fe0..4f2d7f7e36b083 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -12,6 +12,7 @@ import type { BlurEvent, // [macOS FocusEvent, + DragEvent, HandledKeyEvent, KeyEvent, GestureResponderEvent, @@ -198,21 +199,21 @@ type PressableBaseProps = $ReadOnly<{ * * @platform macos */ - onDragEnter?: (event: MouseEvent) => void, + onDragEnter?: (event: DragEvent) => void, /** * Fired when a file is dragged out of the Pressable via the mouse. * * @platform macos */ - onDragLeave?: (event: MouseEvent) => void, + onDragLeave?: (event: DragEvent) => void, /** * Fired when a file is dropped on the Pressable via the mouse. * * @platform macos */ - onDrop?: (event: MouseEvent) => void, + onDrop?: (event: DragEvent) => void, /** * The types of dragged files that the Pressable will accept. diff --git a/packages/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/packages/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js index 71f9ebc257b0d9..bb7c577f2dfc40 100755 --- a/packages/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/packages/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -13,8 +13,8 @@ import type {EdgeInsetsOrSizeProp} from '../../StyleSheet/EdgeInsetsPropType'; import type { BlurEvent, FocusEvent, - // [macOS] - MouseEvent, + MouseEvent, // [macOS] + DragEvent, // [macOS] GestureResponderEvent, LayoutChangeEvent, } from '../../Types/CoreEventTypes'; @@ -36,9 +36,9 @@ export type TouchableWithoutFeedbackPropsIOS = { tooltip?: ?string, onMouseEnter?: (event: MouseEvent) => void, onMouseLeave?: (event: MouseEvent) => void, - onDragEnter?: (event: MouseEvent) => void, - onDragLeave?: (event: MouseEvent) => void, - onDrop?: (event: MouseEvent) => void, + onDragEnter?: (event: DragEvent) => void, + onDragLeave?: (event: DragEvent) => void, + onDrop?: (event: DragEvent) => void, draggedTypes?: ?DraggedTypesType, // macOS] }; diff --git a/packages/react-native/Libraries/Components/View/DraggedType.js b/packages/react-native/Libraries/Components/View/DraggedType.js index dc02cf4cb6c02d..2e4ff89ca045d0 100644 --- a/packages/react-native/Libraries/Components/View/DraggedType.js +++ b/packages/react-native/Libraries/Components/View/DraggedType.js @@ -12,10 +12,10 @@ 'use strict'; -export type DraggedType = 'fileUrl'; +export type DraggedType = 'fileUrl' | 'image' | 'string'; export type DraggedTypesType = DraggedType | $ReadOnlyArray; module.exports = { - DraggedTypes: ['fileUrl'], + DraggedTypes: ['fileUrl', 'image', 'string'], }; diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts b/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts index 22c971ded8bfb5..76e8f1bf0cc37e 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts @@ -13,6 +13,7 @@ import {GestureResponderHandlers} from '../../../types/public/ReactNativeRendere import {StyleProp} from '../../StyleSheet/StyleSheet'; import {ViewStyle} from '../../StyleSheet/StyleSheetTypes'; import { + DragEvent, HandledKeyEvent, KeyEvent, LayoutChangeEvent, @@ -108,7 +109,7 @@ export interface ViewPropsAndroid { tabIndex?: 0 | -1 | undefined; } -export type DraggedType = 'fileUrl'; +export type DraggedType = 'fileUrl' | 'image' | 'string'; export type DraggedTypesType = DraggedType | DraggedType[]; export interface ViewPropsMacOS { @@ -118,9 +119,9 @@ export interface ViewPropsMacOS { enableFocusRing?: boolean | undefined; onMouseEnter?: ((event: MouseEvent) => void) | undefined; onMouseLeave?: ((event: MouseEvent) => void) | undefined; - onDragEnter?: ((event: MouseEvent) => void) | undefined; - onDragLeave?: ((event: MouseEvent) => void) | undefined; - onDrop?: ((event: MouseEvent) => void) | undefined; + onDragEnter?: ((event: DragEvent) => void) | undefined; + onDragLeave?: ((event: DragEvent) => void) | undefined; + onDrop?: ((event: DragEvent) => void) | undefined; onKeyDown?: ((event: KeyEvent) => void) | undefined; onKeyUp?: ((event: KeyEvent) => void) | undefined; keyDownEvents?: HandledKeyEvent[] | undefined; diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index 5fd3186776963c..37532c8cf330fe 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -16,6 +16,7 @@ import type { BlurEvent, FocusEvent, // [macOS] + DragEvent, HandledKeyEvent, KeyEvent, LayoutChangeEvent, @@ -410,21 +411,21 @@ type MacOSViewProps = $ReadOnly<{| * * @platform macos */ - onDragEnter?: (event: MouseEvent) => void, + onDragEnter?: (event: DragEvent) => void, /** * Fired when a file is dragged out of the view via the mouse. * * @platform macos */ - onDragLeave?: (event: MouseEvent) => void, + onDragLeave?: (event: DragEvent) => void, /** * Fired when an element is dropped on a valid drop target * * @platform macos */ - onDrop?: (event: MouseEvent) => void, + onDrop?: (event: DragEvent) => void, /** * Specifies the Tooltip for the view diff --git a/packages/react-native/Libraries/Types/CoreEventTypes.d.ts b/packages/react-native/Libraries/Types/CoreEventTypes.d.ts index 5e672c61baed8e..822ae7cccba033 100644 --- a/packages/react-native/Libraries/Types/CoreEventTypes.d.ts +++ b/packages/react-native/Libraries/Types/CoreEventTypes.d.ts @@ -305,4 +305,26 @@ export interface NativeBlurEvent extends TargetedEvent {} export interface FocusEvent extends NativeSyntheticEvent {} export interface BlueEvent extends NativeSyntheticEvent {} + +// Drag and Drop types +export interface DataTransferItem { + name: string; + kind: string; + type: string; + uri: string; + size?: number | undefined; + width?: number | undefined; + height?: number | undefined; +} + +export interface DataTransfer { + files: ReadonlyArray; + types: ReadonlyArray; +} + +export interface DragEvent extends MouseEvent { + nativeEvent: NativeMouseEvent & { + dataTransfer?: DataTransfer | undefined; + }; +} // macOS] diff --git a/packages/react-native/Libraries/Types/CoreEventTypes.js b/packages/react-native/Libraries/Types/CoreEventTypes.js index b568c59f6bcaab..66321c01cb1e56 100644 --- a/packages/react-native/Libraries/Types/CoreEventTypes.js +++ b/packages/react-native/Libraries/Types/CoreEventTypes.js @@ -370,3 +370,59 @@ export type MouseEvent = NativeSyntheticEvent< timestamp: number, }>, >; + +// // Base mouse event data shared between MouseEvent and DragEvent +// export type NativeMouseEvent = $ReadOnly<{ +// clientX: number, +// clientY: number, +// pageX: number, +// pageY: number, +// timestamp: number, +// screenX?: number, +// screenY?: number, +// altKey?: boolean, +// ctrlKey?: boolean, +// shiftKey?: boolean, +// metaKey?: boolean, +// }>; + +// export type MouseEvent = NativeSyntheticEvent< +// $ReadOnly<{ +// ...NativeMouseEvent, +// dataTransfer?: DataTransfer, +// }>, +// >; + +// [macOS +export type DataTransferItem = $ReadOnly<{ + name: string, + kind: string, + type: string, + uri: string, + size?: number, + width?: number, + height?: number, +}>; + +export type DataTransfer = $ReadOnly<{ + files: $ReadOnlyArray, + types: $ReadOnlyArray, +}>; + +export type DragEvent = NativeSyntheticEvent< + $ReadOnly<{ + clientX: number, + clientY: number, + pageX: number, + pageY: number, + timestamp: number, + screenX?: number, + screenY?: number, + altKey?: boolean, + ctrlKey?: boolean, + shiftKey?: boolean, + metaKey?: boolean, + dataTransfer?: DataTransfer, + }>, +>; +// macOS] diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 0938ea54dbfdc6..568bffab2fed56 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -1744,9 +1744,9 @@ type PressableBaseProps = $ReadOnly<{ enableFocusRing?: ?boolean, allowsVibrancy?: ?boolean, tooltip?: ?string, - onDragEnter?: (event: MouseEvent) => void, - onDragLeave?: (event: MouseEvent) => void, - onDrop?: (event: MouseEvent) => void, + onDragEnter?: (event: DragEvent) => void, + onDragLeave?: (event: DragEvent) => void, + onDrop?: (event: DragEvent) => void, draggedTypes?: ?DraggedTypesType, style?: | ViewStyleProp @@ -3774,9 +3774,9 @@ exports[`public API should not change unintentionally Libraries/Components/Touch tooltip?: ?string, onMouseEnter?: (event: MouseEvent) => void, onMouseLeave?: (event: MouseEvent) => void, - onDragEnter?: (event: MouseEvent) => void, - onDragLeave?: (event: MouseEvent) => void, - onDrop?: (event: MouseEvent) => void, + onDragEnter?: (event: DragEvent) => void, + onDragLeave?: (event: DragEvent) => void, + onDrop?: (event: DragEvent) => void, draggedTypes?: ?DraggedTypesType, }; export type TouchableWithoutFeedbackPropsAndroid = { @@ -3841,7 +3841,7 @@ declare export default typeof UnimplementedView; `; exports[`public API should not change unintentionally Libraries/Components/View/DraggedType.js 1`] = ` -"export type DraggedType = \\"fileUrl\\"; +"export type DraggedType = \\"fileUrl\\" | \\"image\\" | \\"string\\"; export type DraggedTypesType = DraggedType | $ReadOnlyArray; declare module.exports: { DraggedTypes: $FlowFixMe }; " @@ -4212,9 +4212,9 @@ export type ViewPropsIOS = $ReadOnly<{ shouldRasterizeIOS?: ?boolean, }>; type MacOSViewProps = $ReadOnly<{| - onDragEnter?: (event: MouseEvent) => void, - onDragLeave?: (event: MouseEvent) => void, - onDrop?: (event: MouseEvent) => void, + onDragEnter?: (event: DragEvent) => void, + onDragLeave?: (event: DragEvent) => void, + onDrop?: (event: DragEvent) => void, tooltip?: ?string, acceptsFirstMouse?: ?boolean, mouseDownCanMoveWindow?: ?boolean, @@ -8572,6 +8572,35 @@ export type MouseEvent = NativeSyntheticEvent< timestamp: number, }>, >; +export type DataTransferItem = $ReadOnly<{ + name: string, + kind: string, + type: string, + uri: string, + size?: number, + width?: number, + height?: number, +}>; +export type DataTransfer = $ReadOnly<{ + files: $ReadOnlyArray, + types: $ReadOnlyArray, +}>; +export type DragEvent = NativeSyntheticEvent< + $ReadOnly<{ + clientX: number, + clientY: number, + pageX: number, + pageY: number, + timestamp: number, + screenX?: number, + screenY?: number, + altKey?: boolean, + ctrlKey?: boolean, + shiftKey?: boolean, + metaKey?: boolean, + dataTransfer?: DataTransfer, + }>, +>; " `; From 174c387fba58b28bebcc128d98dda94adbe9e0fc Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 17:47:41 -0700 Subject: [PATCH 19/23] update example --- .../js/examples/DragAndDrop/DragAndDropExample.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js index 22338568921752..13ea59327deeac 100644 --- a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js +++ b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js @@ -54,8 +54,8 @@ function DragDropView(): React.Node { onDrop={e => { appendLog('onDrop'); setIsDraggingOver(false); - if (e.nativeEvent.dataTransfer.files && e.nativeEvent.dataTransfer.files[0]) { - const file = e.nativeEvent.dataTransfer.files[0]; + const file = e.nativeEvent.dataTransfer?.files?.[0]; + if (file) { if (file.type.startsWith('image/')) { appendLog('Dropped image file: ' + file.name); } else { @@ -164,7 +164,10 @@ function OnPaste(): React.Node { style={styles.multiline} onPaste={(e: PasteEvent) => { appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types)); - setImageUri(e.nativeEvent.dataTransfer.files[0].uri); + const file = e.nativeEvent.dataTransfer?.files?.[0]; + if (file) { + setImageUri(file.uri); + } }} pastedTypes={['string']} placeholder="MULTI LINE with onPaste() text from clipboard" @@ -174,7 +177,10 @@ function OnPaste(): React.Node { style={styles.multiline} onPaste={(e: PasteEvent) => { appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types)); - setImageUri(e.nativeEvent.dataTransfer.files[0].uri); + const file = e.nativeEvent.dataTransfer?.files?.[0]; + if (file) { + setImageUri(file.uri); + } }} pastedTypes={['fileUrl', 'image', 'string']} placeholder="MULTI LINE with onPaste() for PNG/TIFF images from clipboard or fileUrl (via Finder) and text from clipboard" From 0957cc8a0e940dff4e4d55d2b8ea554479a51e4d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 3 Oct 2025 17:51:45 -0700 Subject: [PATCH 20/23] prettier fix --- .../js/examples/DragAndDrop/DragAndDropExample.js | 11 +++++++---- .../js/examples/TextInput/TextInputExample.ios.js | 5 +---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js index 13ea59327deeac..c873886ac65dc9 100644 --- a/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js +++ b/packages/rn-tester/js/examples/DragAndDrop/DragAndDropExample.js @@ -24,7 +24,6 @@ const styles = StyleSheet.create({ }, }); - function DragDropView(): React.Node { // $FlowFixMe[missing-empty-array-annot] const [log, setLog] = React.useState([]); @@ -75,7 +74,8 @@ function DragDropView(): React.Node { alignItems: 'center', marginVertical: 10, }}> - + {isDraggingOver ? 'Release to drop' : 'Drop an image or file here'} @@ -85,7 +85,9 @@ function DragDropView(): React.Node { {log.join('\n')} - Dropped Image: + + Dropped Image: + Date: Mon, 6 Oct 2025 09:52:55 -0700 Subject: [PATCH 21/23] fixup types --- .../Libraries/Types/CoreEventTypes.js | 122 +++++++----------- .../__snapshots__/public-api-test.js.snap | 64 +++++---- 2 files changed, 76 insertions(+), 110 deletions(-) diff --git a/packages/react-native/Libraries/Types/CoreEventTypes.js b/packages/react-native/Libraries/Types/CoreEventTypes.js index 66321c01cb1e56..54f837e31fc0e0 100644 --- a/packages/react-native/Libraries/Types/CoreEventTypes.js +++ b/packages/react-native/Libraries/Types/CoreEventTypes.js @@ -221,13 +221,10 @@ export interface NativePointerEvent extends NativeMouseEvent { export type PointerEvent = NativeSyntheticEvent; export type NativeTouchEvent = $ReadOnly<{ - altKey?: ?boolean, // [macOS] - button?: ?number, // [macOS] /** * Array of all touch events that have changed since the last event */ changedTouches: $ReadOnlyArray, - ctrlKey?: ?boolean, // [macOS] /** * 3D Touch reported force * @platform ios @@ -245,7 +242,7 @@ export type NativeTouchEvent = $ReadOnly<{ * The Y position of the touch, relative to the element */ locationY: number, - metaKey?: ?boolean, // [macOS] + /** * The X position of the touch, relative to the screen */ @@ -254,7 +251,6 @@ export type NativeTouchEvent = $ReadOnly<{ * The Y position of the touch, relative to the screen */ pageY: number, - shiftKey?: ?boolean, // [macOS] /** * The node id of the element receiving the touch event */ @@ -267,6 +263,13 @@ export type NativeTouchEvent = $ReadOnly<{ * Array of all current touches on the screen */ touches: $ReadOnlyArray, + // [macOS + ctrlKey?: ?boolean, + altKey?: ?boolean, + shiftKey?: ?boolean, + metaKey?: ?boolean, + button?: ?number, + // macOS] }>; export type GestureResponderEvent = ResponderSyntheticEvent; @@ -283,48 +286,6 @@ export type NativeScrollPoint = $ReadOnly<{ x: number, }>; -// [macOS -export type KeyEvent = NativeSyntheticEvent< - $ReadOnly<{| - // Modifier keys - capsLockKey: boolean, - shiftKey: boolean, - ctrlKey: boolean, - altKey: boolean, - metaKey: boolean, - numericPadKey: boolean, - helpKey: boolean, - functionKey: boolean, - // Key options - ArrowLeft: boolean, - ArrowRight: boolean, - ArrowUp: boolean, - ArrowDown: boolean, - key: string, - |}>, ->; - -/** - * Represents a key that could be passed to `KeyDownEvents` and `KeyUpEvents`. - * - * `key` is the actual key, such as "a", or one of the special values: - * "Tab", "Escape", "Enter", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", - * "Backspace", "Delete", "Home", "End", "PageUp", "PageDown". - * - * The rest are modifiers that when absent mean false. - * - * @platform macos - */ -export type HandledKeyEvent = $ReadOnly<{| - altKey?: ?boolean, - ctrlKey?: ?boolean, - metaKey?: ?boolean, - shiftKey?: ?boolean, - key: string, -|}>; - -// macOS] - export type NativeScrollVelocity = $ReadOnly<{ y: number, x: number, @@ -371,28 +332,6 @@ export type MouseEvent = NativeSyntheticEvent< }>, >; -// // Base mouse event data shared between MouseEvent and DragEvent -// export type NativeMouseEvent = $ReadOnly<{ -// clientX: number, -// clientY: number, -// pageX: number, -// pageY: number, -// timestamp: number, -// screenX?: number, -// screenY?: number, -// altKey?: boolean, -// ctrlKey?: boolean, -// shiftKey?: boolean, -// metaKey?: boolean, -// }>; - -// export type MouseEvent = NativeSyntheticEvent< -// $ReadOnly<{ -// ...NativeMouseEvent, -// dataTransfer?: DataTransfer, -// }>, -// >; - // [macOS export type DataTransferItem = $ReadOnly<{ name: string, @@ -416,13 +355,46 @@ export type DragEvent = NativeSyntheticEvent< pageX: number, pageY: number, timestamp: number, - screenX?: number, - screenY?: number, - altKey?: boolean, - ctrlKey?: boolean, - shiftKey?: boolean, - metaKey?: boolean, dataTransfer?: DataTransfer, }>, >; + +export type KeyEvent = NativeSyntheticEvent< + $ReadOnly<{| + // Modifier keys + capsLockKey: boolean, + shiftKey: boolean, + ctrlKey: boolean, + altKey: boolean, + metaKey: boolean, + numericPadKey: boolean, + helpKey: boolean, + functionKey: boolean, + // Key options + ArrowLeft: boolean, + ArrowRight: boolean, + ArrowUp: boolean, + ArrowDown: boolean, + key: string, + |}>, +>; + +/** + * Represents a key that could be passed to `KeyDownEvents` and `KeyUpEvents`. + * + * `key` is the actual key, such as "a", or one of the special values: + * "Tab", "Escape", "Enter", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", + * "Backspace", "Delete", "Home", "End", "PageUp", "PageDown". + * + * The rest are modifiers that when absent mean false. + * + * @platform macos + */ +export type HandledKeyEvent = $ReadOnly<{| + ctrlKey?: ?boolean, + altKey?: ?boolean, + shiftKey?: ?boolean, + metaKey?: ?boolean, + key: string, +|}>; // macOS] diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 568bffab2fed56..c00e8ab99622f3 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -8486,21 +8486,21 @@ export interface NativePointerEvent extends NativeMouseEvent { } export type PointerEvent = NativeSyntheticEvent; export type NativeTouchEvent = $ReadOnly<{ - altKey?: ?boolean, - button?: ?number, changedTouches: $ReadOnlyArray, - ctrlKey?: ?boolean, force?: number, identifier: number, locationX: number, locationY: number, - metaKey?: ?boolean, pageX: number, pageY: number, - shiftKey?: ?boolean, target: ?number, timestamp: number, touches: $ReadOnlyArray, + ctrlKey?: ?boolean, + altKey?: ?boolean, + shiftKey?: ?boolean, + metaKey?: ?boolean, + button?: ?number, }>; export type GestureResponderEvent = ResponderSyntheticEvent; export type NativeScrollRectangle = $ReadOnly<{ @@ -8513,30 +8513,6 @@ export type NativeScrollPoint = $ReadOnly<{ y: number, x: number, }>; -export type KeyEvent = NativeSyntheticEvent< - $ReadOnly<{| - capsLockKey: boolean, - shiftKey: boolean, - ctrlKey: boolean, - altKey: boolean, - metaKey: boolean, - numericPadKey: boolean, - helpKey: boolean, - functionKey: boolean, - ArrowLeft: boolean, - ArrowRight: boolean, - ArrowUp: boolean, - ArrowDown: boolean, - key: string, - |}>, ->; -export type HandledKeyEvent = $ReadOnly<{| - altKey?: ?boolean, - ctrlKey?: ?boolean, - metaKey?: ?boolean, - shiftKey?: ?boolean, - key: string, -|}>; export type NativeScrollVelocity = $ReadOnly<{ y: number, x: number, @@ -8592,15 +8568,33 @@ export type DragEvent = NativeSyntheticEvent< pageX: number, pageY: number, timestamp: number, - screenX?: number, - screenY?: number, - altKey?: boolean, - ctrlKey?: boolean, - shiftKey?: boolean, - metaKey?: boolean, dataTransfer?: DataTransfer, }>, >; +export type KeyEvent = NativeSyntheticEvent< + $ReadOnly<{| + capsLockKey: boolean, + shiftKey: boolean, + ctrlKey: boolean, + altKey: boolean, + metaKey: boolean, + numericPadKey: boolean, + helpKey: boolean, + functionKey: boolean, + ArrowLeft: boolean, + ArrowRight: boolean, + ArrowUp: boolean, + ArrowDown: boolean, + key: string, + |}>, +>; +export type HandledKeyEvent = $ReadOnly<{| + ctrlKey?: ?boolean, + altKey?: ?boolean, + shiftKey?: ?boolean, + metaKey?: ?boolean, + key: string, +|}>; " `; From 23c38b1362224b841de3ea2a00bb3ce6e78a6007 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 6 Oct 2025 10:12:23 -0700 Subject: [PATCH 22/23] use UTTypes --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 53fb1407114df3..a1870b10e5bd3f 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -11,6 +11,9 @@ #import #import #import +#if TARGET_OS_OSX // [macOS +#import +#endif // macOS] #import #import @@ -1751,7 +1754,7 @@ - (void)buildDataTransferItems:(std::vector &)dataTransferItem NSPasteboardType imageType = [pasteboard availableTypeFromArray:@[NSPasteboardTypePNG, NSPasteboardTypeTIFF]]; if (imageType && fileNames.count == 0) { - NSString *MIMETypeString = imageType == NSPasteboardTypePNG ? @"image/png" : @"image/tiff"; + NSString *MIMETypeString = imageType == NSPasteboardTypePNG ?[UTTypePNG preferredMIMEType] : [UTTypeTIFF preferredMIMEType]; NSData *imageData = [pasteboard dataForType:imageType]; NSImage *image = [[NSImage alloc] initWithData:imageData]; CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil]; From dcbd4394c6dbf180eabadf0b49369ab5e25df868 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Mon, 6 Oct 2025 12:38:09 -0700 Subject: [PATCH 23/23] missing endif --- .../Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 21e186652bbf8c..d3defbfbb9c6ca 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -623,6 +623,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.toolTip = nil; } } +#endif // [macOS] _needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer;