From c437f2ff1c1f9ffc03a2cc3ca0c910e41f86aa1c Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 25 Aug 2023 21:50:46 +0200 Subject: [PATCH 01/80] [fabric] Make text input first responder through the window instance Summary: Fix AppKit exception throws when focusing text inputs by calling becomeFirstResponder directly on the backing text input view. Making a view first responder has to happen through the window using makeFirstResponder. Test Plan: - Run Zeratul with Fabric - Focus the search text input above the message threads - Click inside the active message thread to trigger the auto-focus of the composer - The composer gets focus without AppKit throwing an exception. https://pxl.cl/3dVMx Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D48696690 --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 2c12cfa47909ab..57c2ad11af8a4f 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -503,7 +503,14 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args - (void)focus { +#if !TARGET_OS_OSX // [macOS] [_backedTextInputView becomeFirstResponder]; +#else // [macOS + NSWindow *window = _backedTextInputView.window; + if (window) { + [window makeFirstResponder:_backedTextInputView]; + } +#endif // macOS] } - (void)blur From 355f73bf11b4f5e1246efc72a681d84c0455ac95 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 8 Sep 2023 04:22:54 +0200 Subject: [PATCH 02/80] [fabric] Fix hit testing calls going through LegacyViewManagerInteropComponent Summary: Hit testing in RN macOS uses the view coordinate space instead of the macOS superview coordinate space. The RCTView hitTest implementation gets called from Fabric when Paper components are loaded through the `LegacyViewManagerInteropComponent`. When the Paper component has Fabric child components, the hitTest implementation would treat those as native macOS views. This diff adds a selector check to detect Fabric RCTViewComponentView instances and apply the right point conversion. The selector check allows to have the right behavior without having to import Fabric classes in Paper code. Test Plan: - Run Workplace Chat with Fabric enabled - Click on a thread title in the thread list - With the fix, the thread gets selected Reviewers: shawndempsey, ericroz, chpurrer, #rn-desktop Reviewed By: shawndempsey, ericroz Differential Revision: https://phabricator.intern.facebook.com/D49083593 Tasks: T163162857 --- packages/react-native/React/Views/RCTView.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Views/RCTView.m b/packages/react-native/React/Views/RCTView.m index 709e92d20c7c40..0c678c9a82a836 100644 --- a/packages/react-native/React/Views/RCTView.m +++ b/packages/react-native/React/Views/RCTView.m @@ -258,9 +258,11 @@ - (RCTPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event // [macOS #if !TARGET_OS_OSX // [macOS] pointForHitTest = [subview convertPoint:point fromView:self]; #else // [macOS - if ([subview isKindOfClass:[RCTView class]]) { + // Paper and Fabric components use the target view coordinate space for hit testing + if ([subview isKindOfClass:[RCTView class]] || [subview respondsToSelector:@selector(updateProps:oldProps:)]) { pointForHitTest = [subview convertPoint:point fromView:self]; } else { + // Native macOS views require the point to be in the super view coordinate space for hit testing. pointForHitTest = point; } #endif // macOS] From 35259d880abdff826e065a3e694bf9b5e1dfe7df Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 13 Sep 2023 20:27:11 +0200 Subject: [PATCH 03/80] [fabric] Add support for clipsToBounds to RCTViewComponentView Summary: This diff conditionally sets the CALayer masksToBounds based on the clipsToBounds RN property so that the content of the view would be clipped by the view border. Test Plan: - Run Zeratul with Fabric enabled - Check that the profile pictures with no status indicator are clipped by the half size border radius set on the parent view. | Before | After | |--| | {F1090251649} | {F1090249259} | Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D49239973 --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 5 +++++ 1 file changed, 5 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 afd8d23a5d989a..cb324d6392ae55 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -653,6 +653,11 @@ - (void)invalidateLayer return; } +#if TARGET_OS_OSX // [macOS + // clipsToBounds is stubbed out on macOS because it's not part of NSView + layer.masksToBounds = self.clipsToBounds; +#endif // macOS] + const auto borderMetrics = _props->resolveBorderMetrics(_layoutMetrics); // Stage 1. Shadow Path From db854b238028397dcc0aaf59c1a9354189ee1dbd Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 20 Sep 2023 18:25:38 +0200 Subject: [PATCH 04/80] [fabric] Add text storage getter to set up text views outside text layout manager Summary: To render text using an NSTextView, we need to gain access to a fully configured NSTextStorage to configure the text view to render the text with the right configuration. Test Plan: Tested later in this stack. Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D49465173 Tasks: T163838519 --- .../renderer/textlayoutmanager/RCTTextLayoutManager.h | 6 ++++++ .../textlayoutmanager/RCTTextLayoutManager.mm | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h index f94044409cdef5..58d06e3a14815b 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.h @@ -62,6 +62,12 @@ using RCTTextLayoutFragmentEnumerationBlock = frame:(CGRect)frame usingBlock:(RCTTextLayoutFragmentEnumerationBlock)block; +#if TARGET_OS_OSX // [macOS +- (NSTextStorage *)getTextStorageForAttributedString:(facebook::react::AttributedString)attributedString + paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes + frame:(CGRect)frame; +#endif // macOS] + @end NS_ASSUME_NONNULL_END diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm index d134df88154e47..e243fe1cc8ef8f 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm @@ -335,4 +335,15 @@ - (TextMeasurement)_measureTextStorage:(NSTextStorage *)textStorage return TextMeasurement{{size.width, size.height}, attachments}; } +#if TARGET_OS_OSX // [macOS +- (NSTextStorage *)getTextStorageForAttributedString:(AttributedString)attributedString + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + frame:(CGRect)frame +{ + NSTextStorage *textStorage = [self textStorageForAttributesString:attributedString paragraphAttributes:paragraphAttributes size:frame.size]; + + return textStorage; +} +#endif // macOS] + @end From de055ae14cf5fa6f430c8a341b04c2e26b0e670b Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 20 Sep 2023 18:42:18 +0200 Subject: [PATCH 05/80] [fabric] Render Paragraph text using NSTextView Summary: Use an NSTextView to render the text in RCTParagraphComponentView so that we would get UX interactions specific to desktop for free (e.g. text selection) Test Plan: - Run Zeratul with Fabric enabled - Check that text is being rendered correctly with the right size and positioning. {F1097505779} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D49465175 Tasks: T163838519 --- .../Text/RCTParagraphComponentView.mm | 116 +++++++++++++++++- 1 file changed, 111 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 74026ca2292976..dfcd5bc1136979 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -27,22 +27,53 @@ using namespace facebook::react; +#if TARGET_OS_OSX // [macOS +@interface RCTParagraphComponentUnfocusableTextView : NSTextView +@end + +@implementation RCTParagraphComponentUnfocusableTextView + +- (BOOL)canBecomeKeyView +{ + return NO; +} + +- (BOOL)resignFirstResponder +{ + // Don't relinquish first responder while selecting text. + if (self.selectable && NSRunLoop.currentRunLoop.currentMode == NSEventTrackingRunLoopMode) { + return NO; + } + + return [super resignFirstResponder]; +} + +@end +#endif // macOS] + #if !TARGET_OS_OSX // [macOS] @interface RCTParagraphComponentView () @property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0)); @end -#endif // [macOS] @implementation RCTParagraphComponentView { ParagraphShadowNode::ConcreteState::Shared _state; ParagraphAttributes _paragraphAttributes; RCTParagraphComponentAccessibilityProvider *_accessibilityProvider; -#if !TARGET_OS_OSX // [macOS] + UILongPressGestureRecognizer *_longPressGestureRecognizer; -#endif // [macOS] + CAShapeLayer *_highlightLayer; +} +#else // [macOS +@implementation RCTParagraphComponentView { + ParagraphShadowNode::ConcreteState::Shared _state; + ParagraphAttributes _paragraphAttributes; + RCTParagraphComponentAccessibilityProvider *_accessibilityProvider; + RCTParagraphComponentUnfocusableTextView *_textView; } +#endif // macOS] - (instancetype)initWithFrame:(CGRect)frame { @@ -53,7 +84,26 @@ - (instancetype)initWithFrame:(CGRect)frame #if !TARGET_OS_OSX // [macOS] self.contentMode = UIViewContentModeRedraw; self.opaque = NO; -#endif // [macOS] +#else // [macOS + // Make the RCTParagraphComponentView accessible and available in the a11y hierarchy. + self.accessibilityElement = YES; + self.accessibilityRole = NSAccessibilityStaticTextRole; + // Fix blurry text on non-retina displays. + self.canDrawSubviewsIntoLayer = YES; + // The NSTextView is responsible for drawing text and managing selection. + _textView = [[RCTParagraphComponentUnfocusableTextView alloc] initWithFrame:self.bounds]; + // The RCTParagraphComponentUnfocusableTextView is only used for rendering and should not appear in the a11y hierarchy. + _textView.accessibilityElement = NO; + _textView.usesFontPanel = NO; + _textView.drawsBackground = NO; + _textView.linkTextAttributes = @{}; + _textView.editable = NO; + _textView.selectable = NO; + _textView.verticallyResizable = NO; + _textView.layoutManager.usesFontLeading = NO; + self.contentView = _textView; + self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; +#endif // macOS] } return self; @@ -108,7 +158,9 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & } else { [self disableContextMenu]; } -#endif // [macOS] +#else // [macOS + _textView.selectable = newParagraphProps.isSelectable; +#endif // macOS] } [super updateProps:props oldProps:oldProps]; @@ -117,8 +169,58 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState { _state = std::static_pointer_cast(state); +#if !TARGET_OS_OSX // [macOS] + [self setNeedsDisplay]; +#else // [macOS + [self _updateTextView]; +#endif // macOS] +} + +#if TARGET_OS_OSX // [macOS +- (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics + oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics +{ + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + [self _updateTextView]; +} + +- (void)_updateTextView +{ + if (!_state) { + return; + } + + auto textLayoutManager = _state->getData().paragraphLayoutManager.getTextLayoutManager(); + + if (!textLayoutManager) { + return; + } + + RCTTextLayoutManager *nativeTextLayoutManager = + (RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager()); + + CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame()); + + NSTextStorage *textStorage = [nativeTextLayoutManager getTextStorageForAttributedString:_state->getData().attributedString paragraphAttributes:_paragraphAttributes frame:frame]; + + NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; + NSTextContainer *textContainer = layoutManager.textContainers.firstObject; + + [_textView replaceTextContainer:textContainer]; + + NSArray *managers = [[textStorage layoutManagers] copy]; + for (NSLayoutManager *manager in managers) { + [textStorage removeLayoutManager:manager]; + } + + _textView.minSize = frame.size; + _textView.maxSize = frame.size; + _textView.frame = frame; + _textView.textStorage.attributedString = textStorage; + [self setNeedsDisplay]; } +#endif // macOS] - (void)prepareForRecycle { @@ -129,6 +231,10 @@ - (void)prepareForRecycle - (void)drawRect:(CGRect)rect { +#if TARGET_OS_OSX // [macOS + return; +#endif // macOS] + if (!_state) { return; } From 71fae19c0bb4b910f83fcc27ab2962fa755e6038 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 20 Sep 2023 18:48:50 +0200 Subject: [PATCH 06/80] [fabric] Support selection of Paragraph component text content Summary: Override the hitTest and mouseDown handler in `RCTParagraphComponentView` to forward mouse drag events to the underlying NSTextView to get text selection support in Fabric with correct rendering. Test Plan: - Run Zeratul with Fabric enabled. - Select text https://pxl.cl/3q3b3 Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D49465174 Tasks: T163838519 --- .../Text/RCTParagraphComponentView.mm | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index dfcd5bc1136979..33931be8b92d5e 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -10,7 +10,9 @@ #if !TARGET_OS_OSX // [macOS] #import -#endif // [macOS] +#else // [macOS +#import +#endif // macOS] #import #import @@ -48,6 +50,9 @@ - (BOOL)resignFirstResponder return [super resignFirstResponder]; } +@end + +@interface RCTParagraphComponentView () @end #endif // macOS] @@ -92,6 +97,7 @@ - (instancetype)initWithFrame:(CGRect)frame self.canDrawSubviewsIntoLayer = YES; // The NSTextView is responsible for drawing text and managing selection. _textView = [[RCTParagraphComponentUnfocusableTextView alloc] initWithFrame:self.bounds]; + _textView.delegate = self; // The RCTParagraphComponentUnfocusableTextView is only used for rendering and should not appear in the a11y hierarchy. _textView.accessibilityElement = NO; _textView.usesFontPanel = NO; @@ -373,14 +379,89 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture [menuController showMenuFromView:self rect:self.bounds]; } } -#endif // [macOS] +#else // [macOS +- (NSView *)hitTest:(NSPoint)point +{ + // We will forward mouse click events to the NSTextView ourselves to prevent NSTextView from swallowing events that may be handled in JS (e.g. long press). + NSView *hitView = [super hitTest:point]; + + NSEventType eventType = NSApp.currentEvent.type; + BOOL isMouseClickEvent = NSEvent.pressedMouseButtons > 0; + BOOL isMouseMoveEventType = eventType == NSEventTypeMouseMoved || eventType == NSEventTypeMouseEntered || eventType == NSEventTypeMouseExited || eventType == NSEventTypeCursorUpdate; + BOOL isMouseMoveEvent = !isMouseClickEvent && isMouseMoveEventType; + BOOL isTextViewClick = (hitView && hitView == _textView) && !isMouseMoveEvent; + + return isTextViewClick ? self : hitView; +} +- (void)mouseDown:(NSEvent *)event +{ + if (!_textView.selectable) { + [super mouseDown:event]; + return; + } + + // Double/triple-clicks should be forwarded to the NSTextView. + BOOL shouldForward = event.clickCount > 1; + + if (!shouldForward) { + // Peek at next event to know if a selection should begin. + NSEvent *nextEvent = [self.window nextEventMatchingMask:NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged + untilDate:[NSDate distantFuture] + inMode:NSEventTrackingRunLoopMode + dequeue:NO]; + shouldForward = nextEvent.type == NSEventTypeLeftMouseDragged; + } + + if (shouldForward) { + NSView *contentView = self.window.contentView; + // -[NSView hitTest:] takes coordinates in a view's superview coordinate system. + NSPoint point = [contentView.superview convertPoint:event.locationInWindow fromView:nil]; + + // Start selection if we're still selectable and hit-testable. + if (_textView.selectable && [contentView hitTest:point] == self) { + [[RCTSurfaceTouchHandler surfaceTouchHandlerForView:self] cancelTouchWithEvent:event]; + [self.window makeFirstResponder:_textView]; + [_textView mouseDown:event]; + } + } else { + // Clear selection for single clicks. + _textView.selectedRange = NSMakeRange(NSNotFound, 0); + } +} + +#pragma mark - Selection + +- (void)textDidEndEditing:(NSNotification *)notification +{ + _textView.selectedRange = NSMakeRange(NSNotFound, 0); +} + +#endif // macOS] + +#if !TARGET_OS_OSX // [macOS] - (BOOL)canBecomeFirstResponder { const auto ¶graphProps = static_cast(*_props); return paragraphProps.isSelectable; } +#else +- (BOOL)becomeFirstResponder +{ + if (![super becomeFirstResponder]) { + return NO; + } + + return YES; +} + +- (BOOL)canBecomeFirstResponder +{ + return self.focusable; +} +#endif // macOS] +#if !TARGET_OS_OSX // [macOS] - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { const auto ¶graphProps = static_cast(*_props); @@ -395,6 +476,7 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender return NO; #endif // macOS] } +#endif // [macOS] - (void)copy:(id)sender { From 1b11edcb3421ec0bd095bc37e796ae6e83d8288d Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 4 Oct 2023 04:12:25 +0200 Subject: [PATCH 07/80] [fabric] Fix events being dispatched to wrong react component for Paper components Summary: The tag value is stored in the `reactTag` property on macOS. The adapter used by the RCTLegacyViewManagerInteropComponentView was being provided the `tag` property value which set all interceptors to tag -1 leading to incorrect event dispatching on the JS side. Test Plan: - Open the settings pane in Zeratul and select the appearance tab. - Toggle the theme PopUpButton With the fix, the theme changes while before the zoom would change. https://pxl.cl/3vZRd Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D49897662 --- .../RCTLegacyViewManagerInteropComponentView.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm index 8377b7b3728716..3a25c02b2e7fb5 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm @@ -181,8 +181,14 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask [super finalizeUpdates:updateMask]; if (!_adapter) { +#if !TARGET_OS_OSX // [macOS] _adapter = [[RCTLegacyViewManagerInteropCoordinatorAdapter alloc] initWithCoordinator:[self _coordinator] reactTag:self.tag]; +#else // [macOS + _adapter = [[RCTLegacyViewManagerInteropCoordinatorAdapter alloc] initWithCoordinator:[self _coordinator] + reactTag:self.reactTag.integerValue]; +#endif // macOS] + __weak __typeof(self) weakSelf = self; _adapter.eventInterceptor = ^(std::string eventName, folly::dynamic event) { if (weakSelf) { From 53f631fd53b1d38618881330d87d5fca95d3ceae Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 4 Oct 2023 21:55:42 +0200 Subject: [PATCH 08/80] [fabric] Fix missing import Summary: See title Test Plan: Build RNTester Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D49924996 --- .../RCTLegacyViewManagerInteropComponentView.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm index 3a25c02b2e7fb5..398c8c8da01dfd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm @@ -15,6 +15,10 @@ #import #import "RCTLegacyViewManagerInteropCoordinatorAdapter.h" +#if TARGET_OS_OSX // [macOS +#import +#endif // macOS] + using namespace facebook::react; static NSString *const kRCTLegacyInteropChildComponentKey = @"childComponentView"; From b53775d628ae4c000ec2e6ae3b9b2707f26fa468 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 9 Oct 2023 19:28:27 +0200 Subject: [PATCH 09/80] [fabric] Fix onPress not working for Text Summary: This overrides the correct `hitTest` method to conditionally forward NSResponder events to the component or the underlying native text view to handle onPress and text selection. RN will use `hitTest:withEvent:` while native components use `hitTest:`. Without overriding the method used by RN, the conditional forwarding is being bypassed and mouse clicks would get forwarded to the underlying native text view component. Test Plan: - Allowlist TU for the zeratul_enable_fabric_preferences_surface GK - Run Zeratul - Open the settings - Select *Active status* - Click on the *Learn more* link With the fix the link loads in the browser: https://pxl.cl/3z70J Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D50087800 Tasks: T166059211 --- .../ComponentViews/Text/RCTParagraphComponentView.mm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 33931be8b92d5e..62db2978f7a591 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -380,10 +380,10 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture } } #else // [macOS -- (NSView *)hitTest:(NSPoint)point +- (NSView *)hitTest:(CGPoint)point withEvent:(NSEvent *)event { // We will forward mouse click events to the NSTextView ourselves to prevent NSTextView from swallowing events that may be handled in JS (e.g. long press). - NSView *hitView = [super hitTest:point]; + NSView *hitView = [super hitTest:point withEvent:event]; NSEventType eventType = NSApp.currentEvent.type; BOOL isMouseClickEvent = NSEvent.pressedMouseButtons > 0; @@ -394,6 +394,11 @@ - (NSView *)hitTest:(NSPoint)point return isTextViewClick ? self : hitView; } +- (NSView *)hitTest:(NSPoint)point +{ + return [self hitTest:point withEvent:NSApp.currentEvent]; +} + - (void)mouseDown:(NSEvent *)event { if (!_textView.selectable) { From 5ace629b217ff9e24efcea8dc2ececb2b6602908 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Thu, 5 Oct 2023 21:51:21 -0700 Subject: [PATCH 10/80] [fabric] Add native focus props to ViewComponentView Summary: **Context** - Our Fabric ViewComponentView supports the focus prop but it is not being sent via native props **Change** - Add the native prop to `ReactCommon/.../ViewProps.h` - Updated `focusable` property on `RCTViewComponentView` Test Plan: |RNTester - Fabric| | https://pxl.cl/3wLwF | |RNTester - Paper| || Reviewers: lefever, #rn-desktop Differential Revision: https://phabricator.intern.facebook.com/D49998664 [upstream][fabric] Add enableFocusRing for Fabric ViewComponentView Summary: **Context** - `focusable` prop was added to Fabric ViewComponent in D49998664 - `enableFocusRing` is available on Paper view **Change** - Add the native prop to `ReactCommon/.../ViewProps.h` - Updated `enableFocusView` property on RCTViewComponentView Test Plan: NOTE: Since views are flattened, the focusable view w/ enable focus ring won't be visible till D50102747 |Fabric| | https://pxl.cl/3z9Wm| Reviewers: lefever, #rn-desktop Reviewed By: lefever Differential Revision: https://phabricator.intern.facebook.com/D50102547 --- .../View/RCTViewComponentView.mm | 7 +++++ .../components/view/BaseViewProps.cpp | 26 +++++++++++++++++++ .../renderer/components/view/BaseViewProps.h | 5 ++++ 3 files changed, 38 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 cb324d6392ae55..3959a6dd1796ad 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -409,6 +409,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.layer.zPosition = newViewProps.zIndex.value_or(0); } +#if TARGET_OS_OSX // [macOS + // `focusable` + self.focusable = (bool)newViewProps.focusable; + // `enableFocusRing` + self.enableFocusRing = (bool)newViewProps.enableFocusRing; +#endif // macOS] + _needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer; _props = std::static_pointer_cast(props); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index 59ab0732b4e9ee..fdd336626bf26e 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -238,6 +238,28 @@ BaseViewProps::BaseViewProps( "removeClippedSubviews", sourceProps.removeClippedSubviews, false)), + +#ifdef TARGET_OS_OSX // [macOS + focusable( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.focusable + : convertRawProp( + context, + rawProps, + "focusable", + sourceProps.focusable, + false)), + enableFocusRing( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.enableFocusRing + : convertRawProp( + context, + rawProps, + "enableFocusRing", + sourceProps.enableFocusRing, + false)), +#endif // macOS] + experimental_layoutConformance( CoreFeatures::enablePropIteratorSetter ? sourceProps.experimental_layoutConformance @@ -290,6 +312,10 @@ void BaseViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(collapsable); RAW_SET_PROP_SWITCH_CASE_BASIC(removeClippedSubviews); RAW_SET_PROP_SWITCH_CASE_BASIC(experimental_layoutConformance); +#ifdef TARGET_OS_OSX // [macOS + RAW_SET_PROP_SWITCH_CASE_BASIC(focusable); + RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing); +#endif // macOS] // events field VIEW_EVENT_CASE(PointerEnter); VIEW_EVENT_CASE(PointerEnterCapture); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index 9e8d56f6d50a5a..203cb0a6430d31 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -74,6 +74,11 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { LayoutConformance experimental_layoutConformance{}; +#ifdef TARGET_OS_OSX // [macOS + bool focusable{false}; + bool enableFocusRing{false}; +#endif // macOS] + #pragma mark - Convenience Methods BorderMetrics resolveBorderMetrics(const LayoutMetrics& layoutMetrics) const; From 28a761f9c91fa92317975458b08464f7eaadbdbb Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Mon, 9 Oct 2023 15:37:39 -0700 Subject: [PATCH 11/80] [fabric] Don't flatten View if focusable or enableFocusRing Summary: **Context** - Focusable props were added in D49998664 and D50102547 - The ViewComponentView is flattened so when the component had children, the focus view would be flattened away. **Change** - Do not flatten the view if `focusable` or `enableFocusView` are props on the Fabric View Test Plan: |Fabric| | https://pxl.cl/3z9X4| Reviewers: lefever, #rn-desktop Reviewed By: lefever Differential Revision: https://phabricator.intern.facebook.com/D50102747 --- .../react/renderer/components/view/ViewShadowNode.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp index 12a9dd40370e34..76ff482ad16fcf 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp @@ -52,7 +52,12 @@ void ViewShadowNode::initialize() noexcept { viewProps.accessibilityViewIsModal || viewProps.importantForAccessibility != ImportantForAccessibility::Auto || viewProps.removeClippedSubviews || viewProps.cursor != Cursor::Auto || - HostPlatformViewTraitsInitializer::formsStackingContext(viewProps); + HostPlatformViewTraitsInitializer::formsStackingContext(viewProps) +#if TARGET_OS_OSX // [macOS + || viewProps.focusable + || viewProps.enableFocusRing +#endif // macOS] + ; bool formsView = formsStackingContext || isColorMeaningful(viewProps.backgroundColor) || From 80b6b44fc28f0c6055566b33a36bfcda94aaaad8 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 25 Oct 2023 02:54:49 +0200 Subject: [PATCH 12/80] [fabric] Adapt transforms to CALayer top-left anchor point Summary: On macOS, Core Animation layers have their anchor point set to (0,0) which is the top-left. On iOS the anchor point is set to (0.5, 0.5) which matches the center. Transforms assigned to views are built based on having the anchor point at the center. This diff updates the transform matrix to apply to transform from the center. Test Plan: Check zoom/rotation animations in Zeratul. | Before | After | |--| | https://pxl.cl/3G8HG | https://pxl.cl/3G8HQ | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D50628807 Tasks: T161413049 --- .../ComponentViews/View/RCTViewComponentView.mm | 12 +++++++++++- .../React/Fabric/Mounting/RCTMountingManager.mm | 15 +++++++++++++++ 2 files changed, 26 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 3959a6dd1796ad..1ac9e10260f43e 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -296,7 +296,17 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & oldViewProps.transformOrigin != newViewProps.transformOrigin) && ![_propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN containsObject:@"transform"]) { auto newTransform = newViewProps.resolveTransform(_layoutMetrics); - self.layer.transform = RCTCATransform3DFromTransformMatrix(newTransform); + CATransform3D transform = RCTCATransform3DFromTransformMatrix(newTransform); +#if TARGET_OS_OSX // [macOS + CGPoint anchorPoint = self.layer.anchorPoint; + if (CGPointEqualToPoint(anchorPoint, CGPointZero) && !CATransform3DEqualToTransform(transform, CATransform3DIdentity)) { + // https://developer.apple.com/documentation/quartzcore/calayer/1410817-anchorpoint + // This compensates for the fact that layer.anchorPoint is {0, 0} instead of {0.5, 0.5} on macOS for some reason. + CATransform3D originAdjust = CATransform3DTranslate(CATransform3DIdentity, self.frame.size.width / 2, self.frame.size.height / 2, 0); + transform = CATransform3DConcat(CATransform3DConcat(CATransform3DInvert(originAdjust), transform), originAdjust); + } +#endif // macOS] + self.layer.transform = transform; self.layer.allowsEdgeAntialiasing = newViewProps.transform != Transform::Identity(); } diff --git a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm index dd60be42d1e1f5..fdcae5e2820dc9 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm @@ -310,6 +310,7 @@ - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag const auto &newViewProps = static_cast(*newProps); +#if !TARGET_OS_OSX // [macOS] if (props[@"transform"]) { auto layoutMetrics = LayoutMetrics(); layoutMetrics.frame.size.width = componentView.layer.bounds.size.width; @@ -318,7 +319,21 @@ - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag if (!CATransform3DEqualToTransform(newTransform, componentView.layer.transform)) { componentView.layer.transform = newTransform; } +#else // [macOS + if (props[@"transform"]) { + CATransform3D transform = RCTCATransform3DFromTransformMatrix(newViewProps.transform); + + if (CGPointEqualToPoint(componentView.layer.anchorPoint, CGPointZero) && !CATransform3DEqualToTransform(transform, CATransform3DIdentity)) { + CATransform3D originAdjust = CATransform3DTranslate(CATransform3DIdentity, componentView.frame.size.width / 2, componentView.frame.size.height / 2, 0); + transform = CATransform3DConcat(CATransform3DConcat(CATransform3DInvert(originAdjust), transform), originAdjust); + } + + if (!CATransform3DEqualToTransform(transform, componentView.layer.transform)) { + componentView.layer.transform = transform; + } } +#endif // macOS] + if (props[@"opacity"] && componentView.layer.opacity != (float)newViewProps.opacity) { componentView.layer.opacity = newViewProps.opacity; } From 0c79cb2b7d60c98e2bd475dd837d4d2e3599f7f1 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 26 Oct 2023 19:25:27 +0200 Subject: [PATCH 13/80] [fabric] Make text input resign first responder through the window instance Summary: Fix AppKit exception throws when blurring text inputs by calling `resignFirstResponder` directly on the backing text input view. Resigning the first responder state has to happen through the window by calling `[window makeFirstResponder:nil]` which will: - call `resignFirstResponder` on the current first responder - if successful, the window will become the first responder Test Plan: - Run Zeratul with Fabric - Focus the search text input above the message threads - Click inside the active message thread to trigger the auto-focus of the composer and the blur on the search field - The focused field resigns the first responder status without throwing an exception https://pxl.cl/3GvZD Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D50700782 --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 57c2ad11af8a4f..dca0d3737f5828 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -515,7 +515,15 @@ - (void)focus - (void)blur { +#if !TARGET_OS_OSX // [macOS] [_backedTextInputView resignFirstResponder]; +#else + NSWindow *window = _backedTextInputView.window; + if (window && window.firstResponder == _backedTextInputView) { + // Calling makeFirstResponder with nil will call resignFirstResponder and make the window the first responder + [window makeFirstResponder:nil]; + } +#endif // macOS]; } - (void)setTextAndSelection:(NSInteger)eventCount From 64b42138fff7ab687c73a077f457837fc080ddaa Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 31 Oct 2023 10:51:00 -0700 Subject: [PATCH 14/80] [fabric] Disable view flattening in VirtualizedList cells Summary: Scroll views set up with an inverted vertical axis can't support view flattening due to the flattening not taking the axis inversion in consideration while repositioning the views. This diff disables view flattening on the cells of the virtualized list so that the layout would be correct in inverted scroll views when using Fabric. The change is not being applied to ScrollView directly because we can safely assume that vertical axis inversion will only be enabled on VirtualizedList/FlatList. Test Plan: Run Zeratul with Fabric and check that the vertical order of grouped bubble messages is correct. | Before | After | |--| | {F1136386200} | {F1136386364} | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D50846483 Tasks: T167539420 --- .../Lists/VirtualizedListCellRenderer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js b/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js index fbb8512e808a5a..0c9cf2ff2c1177 100644 --- a/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js +++ b/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js @@ -20,6 +20,8 @@ import {VirtualizedListCellContextProvider} from './VirtualizedListContext.js'; import invariant from 'invariant'; import * as React from 'react'; +const Platform = require('../Utilities/Platform'); // [macOS] + export type Props = { CellRendererComponent?: ?React.ComponentType>, ItemSeparatorComponent: ?React.ComponentType< @@ -208,7 +210,7 @@ export default class CellRenderer extends React.Component< : horizontal ? [styles.row, inversionStyle] : inversionStyle; - const result = !CellRendererComponent ? ( + let result = !CellRendererComponent ? ( // [macOS] extends React.Component< ); + if (Platform.OS === 'macos') { // [macOS + result = React.cloneElement(result, {collapsable: false}); + } // macOS] + return ( {result} From f74997fa4971714f97d198eacdc2242b3feabcb0 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 8 Nov 2023 22:09:39 +0100 Subject: [PATCH 15/80] [fabric] Add conditional coordinate space conversion for native view hit testing Summary: Native views use the AppKit api for hit testing which requires the point to be in the superview coordinate system. This diff updates the hit testing in `RCTViewComponentView` to conditionally converts the point to the target view coordinate system only if the tested view is a react view. Test Plan: Run Zeratul with Fabric and select text inside message bubbles. The scroll view being a native view, the hit testing does not require a point conversion. With this change, the text selection works as expected. | Before | After | |--| | https://pxl.cl/3Mlpb | https://pxl.cl/3MllN | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D51129375 Tags: uikit-diff --- .../ComponentViews/View/RCTViewComponentView.mm | 12 +++++++++++- 1 file changed, 11 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 1ac9e10260f43e..7e205fe40bd047 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -16,6 +16,7 @@ #import #import // [macOS] #import +#import // [macOS] #import #import #import @@ -539,7 +540,16 @@ - (RCTUIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event // [macOS } for (RCTUIView *subview in [self.subviews reverseObjectEnumerator]) { // [macOS] - RCTUIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event]; // [macOS] + // Native macOS views require the point to be in the super view coordinate space for hit testing. [macOS] + CGPoint hitTestPoint = point; +#if TARGET_OS_OSX // [macOS + // Paper and Fabric components use the target view coordinate space for hit testing + if ([subview isKindOfClass:[RCTView class]] || [subview isKindOfClass:[RCTViewComponentView class]]) { + hitTestPoint = [subview convertPoint:point fromView:self]; + } +#endif // macOS] + + RCTUIView *hitView = [subview hitTest:hitTestPoint withEvent:event]; // [macOS] if (hitView) { return hitView; } From 474fcb9baa041c04785b5e7ba7d8b6cab747502b Mon Sep 17 00:00:00 2001 From: Davis Rollman Date: Wed, 8 Nov 2023 16:54:30 -0800 Subject: [PATCH 16/80] [fabric] Add iterator to SurfaceTelemetry.cpp Summary: Picking out this from D51015931, will import to fbsource. Goal is upgrading visual studio tools on windows from the good ol' ancient vs2017. Test Plan: CI on D46923145 Reviewers: lyahdav, ericroz Reviewed By: ericroz Differential Revision: https://phabricator.intern.facebook.com/D51140141 --- .../ReactCommon/react/renderer/telemetry/SurfaceTelemetry.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactCommon/react/renderer/telemetry/SurfaceTelemetry.cpp b/packages/react-native/ReactCommon/react/renderer/telemetry/SurfaceTelemetry.cpp index 6b4845f64688b7..64d46f8d475e8a 100644 --- a/packages/react-native/ReactCommon/react/renderer/telemetry/SurfaceTelemetry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/telemetry/SurfaceTelemetry.cpp @@ -8,6 +8,7 @@ #include "SurfaceTelemetry.h" #include +#include namespace facebook::react { From fadf8a8d5423b59bb274af43fee5428147d8ac38 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 10 Nov 2023 01:18:12 +0100 Subject: [PATCH 17/80] [fabric] Disable view flattening in header/footer/empty/spacer components of VirtualizedList Summary: View flattening was already disabled in the cell renderer used by the Virtualized List in this diff D50846483 This diff disables view flattening in the header, footer, empty and spacer cells to fix the layout being broken because of the vertical axis flipping used by the reverse order virtualized list. Test Plan: Run Zeratul with Fabric enabled and scroll to the top of a message thread to show the participants summary header. | Before | After | |--| | {F1145726580} | {F1145726618} | Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51182545 Tasks: T167539420 --- packages/virtualized-lists/Lists/VirtualizedList.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index 2a5403ec96bb93..1fa23377ab6b56 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -1005,6 +1005,7 @@ class VirtualizedList extends StateSafePureComponent { cells.push( {React.cloneElement(element, { onLayout: (event: LayoutEvent) => { @@ -1061,6 +1062,7 @@ class VirtualizedList extends StateSafePureComponent { lastMetrics.offset + lastMetrics.length - firstMetrics.offset; cells.push( , @@ -1101,6 +1103,7 @@ class VirtualizedList extends StateSafePureComponent { cellKey={this._getFooterCellKey()} key="$footer"> Date: Wed, 1 Nov 2023 15:46:29 -0700 Subject: [PATCH 18/80] [fabric] Enable Automatic Spelling Correction Summary: **Context** - Port Paper TextInput api's to Fabric https://www.internalfb.com/code/fbsource/[dab91113a70a2f967fa2996f4aca0f49a58aea17]/third-party/microsoft-fork-of-react-native/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.m?lines=440-444 **Change** - Automatic spelling correction works on Fabric TextInput Test Plan: **Enable "Correct Spelling Automatically"** |https://pxl.cl/3MFjJ| https://pxl.cl/3MFx6| {F1146645411} Reviewers: chpurrer, lefever, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51158136 --- .../TextInput/RCTBackedTextInputViewProtocol.h | 1 + .../TextInput/RCTTextInputComponentView.mm | 17 ++++++++++++++--- .../iostextinput/TextInputEventEmitter.cpp | 10 ++++++++++ .../iostextinput/TextInputEventEmitter.h | 7 +++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 6807acc06235fc..ba3e4ebbb95d37 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -62,6 +62,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readonly) UIEdgeInsets contentInset; #if TARGET_OS_OSX // [macOS @property (nonatomic, assign) CGFloat pointScaleFactor; +@property (nonatomic, getter=isAutomaticSpellingCorrectionEnabled) BOOL automaticSpellingCorrectionEnabled; #endif // macOS] // This protocol disallows direct access to `selectedTextRange` property because diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index dca0d3737f5828..e786508d83ca09 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -127,13 +127,20 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _backedTextInputView.autocapitalizationType = RCTUITextAutocapitalizationTypeFromAutocapitalizationType(newTextInputProps.traits.autocapitalizationType); } +#endif +#if !TARGET_OS_OSX // [macOS] if (newTextInputProps.traits.autoCorrect != oldTextInputProps.traits.autoCorrect) { _backedTextInputView.autocorrectionType = RCTUITextAutocorrectionTypeFromOptionalBool(newTextInputProps.traits.autoCorrect); } -#endif // [macOS] - +#else // [macOS + if (newTextInputProps.traits.autoCorrect != oldTextInputProps.traits.autoCorrect && newTextInputProps.traits.autoCorrect.has_value()) { + _backedTextInputView.automaticSpellingCorrectionEnabled = + newTextInputProps.traits.autoCorrect.value(); + } +#endif // macOS] + if (newTextInputProps.traits.contextMenuHidden != oldTextInputProps.traits.contextMenuHidden) { _backedTextInputView.contextMenuHidden = newTextInputProps.traits.contextMenuHidden; } @@ -438,7 +445,11 @@ - (void)textInputDidChangeSelection } #if TARGET_OS_OSX // [macOS -- (void)automaticSpellingCorrectionDidChange:(BOOL)enabled {} +- (void)automaticSpellingCorrectionDidChange:(BOOL)enabled { + if (_eventEmitter) { + std::static_pointer_cast(_eventEmitter)->onAutoCorrectChange({.enabled = enabled}); + } +} - (void)continuousSpellCheckingDidChange:(BOOL)enabled {} diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp index 88ae3f35a221c6..06e35a869b690c 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp @@ -167,4 +167,14 @@ void TextInputEventEmitter::dispatchTextInputContentSizeChangeEvent( priority); } +#if TARGET_OS_OSX // [macOS +void TextInputEventEmitter::onAutoCorrectChange(OnAutoCorrectChange event) const { + dispatchEvent("autoCorrectChange", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "enabled", event.enabled); + return payload; + }); +} +#endif // macOS] + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h index 0ab2b18544fc34..5d6b91d683c95a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h @@ -48,6 +48,13 @@ class TextInputEventEmitter : public ViewEventEmitter { void onKeyPressSync(const KeyPressMetrics& keyPressMetrics) const; void onScroll(const TextInputMetrics& textInputMetrics) const; +#if TARGET_OS_OSX // [macOS + struct OnAutoCorrectChange { + bool enabled; + }; + void onAutoCorrectChange(OnAutoCorrectChange value) const; +#endif // macOS] + private: void dispatchTextInputEvent( const std::string& name, From e8e940d2b8130cdde7500356b06740a3428ffb25 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Thu, 9 Nov 2023 14:56:17 -0800 Subject: [PATCH 19/80] [fabric] Enable Continuous Spell Checking Summary: **Context** - Port Paper TextInput api's to Fabric https://www.internalfb.com/code/fbsource/[25297dddce1b]/third-party/microsoft-fork-of-react-native/react-native-macos/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm?lines=500-505 **Change** - Automatic spell checking works on Fabric TextInput Test Plan: **Enable "Check Spelling While Typing"** https://pxl.cl/3MG01 Reviewers: chpurrer, lefever, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51177294 --- .../RCTBackedTextInputViewProtocol.h | 1 + .../TextInput/RCTTextInputComponentView.mm | 20 ++++++++++++++----- .../TextInput/RCTTextInputUtils.h | 2 ++ .../TextInput/RCTTextInputUtils.mm | 2 ++ .../iostextinput/TextInputEventEmitter.cpp | 8 ++++++++ .../iostextinput/TextInputEventEmitter.h | 5 +++++ 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index ba3e4ebbb95d37..5ad49739b1e562 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -63,6 +63,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_OSX // [macOS @property (nonatomic, assign) CGFloat pointScaleFactor; @property (nonatomic, getter=isAutomaticSpellingCorrectionEnabled) BOOL automaticSpellingCorrectionEnabled; +@property (nonatomic, getter=isContinuousSpellCheckingEnabled) BOOL continuousSpellCheckingEnabled; #endif // macOS] // This protocol disallows direct access to `selectedTextRange` property because diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index e786508d83ca09..30cf4d621e0f07 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -159,12 +159,19 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _backedTextInputView.keyboardAppearance = RCTUIKeyboardAppearanceFromKeyboardAppearance(newTextInputProps.traits.keyboardAppearance); } - +#endif + +#if !TARGET_OS_OSX // [macOS] if (newTextInputProps.traits.spellCheck != oldTextInputProps.traits.spellCheck) { _backedTextInputView.spellCheckingType = RCTUITextSpellCheckingTypeFromOptionalBool(newTextInputProps.traits.spellCheck); } -#endif // [macOS] +#else // [macOS + if (newTextInputProps.traits.spellCheck != oldTextInputProps.traits.spellCheck && newTextInputProps.traits.spellCheck.has_value()) { + _backedTextInputView.continuousSpellCheckingEnabled = + newTextInputProps.traits.spellCheck.value(); + } +#endif // macOS] if (newTextInputProps.traits.caretHidden != oldTextInputProps.traits.caretHidden) { _backedTextInputView.caretHidden = newTextInputProps.traits.caretHidden; @@ -451,9 +458,12 @@ - (void)automaticSpellingCorrectionDidChange:(BOOL)enabled { } } - -- (void)continuousSpellCheckingDidChange:(BOOL)enabled {} - +- (void)continuousSpellCheckingDidChange:(BOOL)enabled +{ + if (_eventEmitter) { + std::static_pointer_cast(_eventEmitter)->onSpellCheckChange({.enabled = enabled}); + } +} - (void)grammarCheckingDidChange:(BOOL)enabled {} diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h index fd4bb5fd03f2c3..a7592cca95469d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h @@ -35,7 +35,9 @@ UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizati UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance( facebook::react::KeyboardAppearance keyboardAppearance); +#endif +#if !TARGET_OS_OSX // [macOS] UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional spellCheck); UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode( diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 319ec09fedcfa5..67fb26dc206748 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -98,7 +98,9 @@ UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(KeyboardAppea return UIKeyboardAppearanceDark; } } +#endif +#if !TARGET_OS_OSX // [macOS] UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional spellCheck) { return spellCheck.has_value() ? (*spellCheck ? UITextSpellCheckingTypeYes : UITextSpellCheckingTypeNo) diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp index 06e35a869b690c..1abd05f205eab1 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp @@ -175,6 +175,14 @@ void TextInputEventEmitter::onAutoCorrectChange(OnAutoCorrectChange event) const return payload; }); } + +void TextInputEventEmitter::onSpellCheckChange(OnSpellCheckChange event) const { + dispatchEvent("spellCheckChange", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "enabled", event.enabled); + return payload; + }); +} #endif // macOS] } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h index 5d6b91d683c95a..a11915b12cc7b1 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h @@ -53,6 +53,11 @@ class TextInputEventEmitter : public ViewEventEmitter { bool enabled; }; void onAutoCorrectChange(OnAutoCorrectChange value) const; + + struct OnSpellCheckChange { + bool enabled; + }; + void onSpellCheckChange(OnSpellCheckChange value) const; #endif // macOS] private: From a1602d3a77812ea7ee9a583b203f072078ee9454 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Thu, 9 Nov 2023 15:36:04 -0800 Subject: [PATCH 20/80] [fabric] Enable Grammar Checking Summary: **Context** - Port Paper TextInput api's to Fabric https://www.internalfb.com/code/fbsource/[25297dddce1b]/third-party/microsoft-fork-of-react-native/react-native-macos/packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm?lines=507-512 **Change** - Automatic grammar checking works on Fabric TextInput Test Plan: Enable "Check Grammar With Spelling" https://pxl.cl/3MWj6 {F1146663064} Reviewers: chpurrer, lefever, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51179163 --- .../TextInput/RCTBackedTextInputViewProtocol.h | 1 + .../TextInput/RCTTextInputComponentView.mm | 15 +++++++++++++-- .../iostextinput/TextInputEventEmitter.cpp | 8 ++++++++ .../iostextinput/TextInputEventEmitter.h | 5 +++++ .../renderer/components/iostextinput/primitives.h | 9 +++++++++ .../components/iostextinput/propsConversions.h | 9 +++++++++ 6 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 5ad49739b1e562..8411f28839556c 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -63,6 +63,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_OSX // [macOS @property (nonatomic, assign) CGFloat pointScaleFactor; @property (nonatomic, getter=isAutomaticSpellingCorrectionEnabled) BOOL automaticSpellingCorrectionEnabled; +@property (nonatomic, getter=isGrammarCheckingEnabled) BOOL grammarCheckingEnabled; @property (nonatomic, getter=isContinuousSpellCheckingEnabled) BOOL continuousSpellCheckingEnabled; #endif // macOS] diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 30cf4d621e0f07..7a00fdee53df90 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -172,6 +172,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & newTextInputProps.traits.spellCheck.value(); } #endif // macOS] + +#if TARGET_OS_OSX // [macOS + if (newTextInputProps.traits.grammarCheck != oldTextInputProps.traits.grammarCheck && newTextInputProps.traits.grammarCheck.has_value()) { + _backedTextInputView.grammarCheckingEnabled = + newTextInputProps.traits.grammarCheck.value(); + } +#endif // macOS] if (newTextInputProps.traits.caretHidden != oldTextInputProps.traits.caretHidden) { _backedTextInputView.caretHidden = newTextInputProps.traits.caretHidden; @@ -465,8 +472,12 @@ - (void)continuousSpellCheckingDidChange:(BOOL)enabled } } -- (void)grammarCheckingDidChange:(BOOL)enabled {} - +- (void)grammarCheckingDidChange:(BOOL)enabled +{ + if (_eventEmitter) { + std::static_pointer_cast(_eventEmitter)->onGrammarCheckChange({.enabled = enabled}); + } +} - (BOOL)hasValidKeyDownOrValidKeyUp:(nonnull NSString *)key { return YES; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp index 1abd05f205eab1..7fec0bb7c23486 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp @@ -183,6 +183,14 @@ void TextInputEventEmitter::onSpellCheckChange(OnSpellCheckChange event) const { return payload; }); } + +void TextInputEventEmitter::onGrammarCheckChange(OnGrammarCheckChange event) const { + dispatchEvent("grammarCheckChange", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "enabled", event.enabled); + return payload; + }); +} #endif // macOS] } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h index a11915b12cc7b1..06e16d9d94cde1 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h @@ -58,6 +58,11 @@ class TextInputEventEmitter : public ViewEventEmitter { bool enabled; }; void onSpellCheckChange(OnSpellCheckChange value) const; + + struct OnGrammarCheckChange { + bool enabled; + }; + void onGrammarCheckChange(OnGrammarCheckChange value) const; #endif // macOS] private: diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h index 664c0ccf0496ac..2057764b8b9ed7 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h @@ -231,6 +231,15 @@ class TextInputTraits final { * Default value: `empty` (`null`). */ std::optional smartInsertDelete{}; + +#ifdef TARGET_OS_OSX // [macOS + /* + * Can be empty (`null` in JavaScript) which means `default`. + * maOS + * Default value: `empty` (`null`). + */ + std::optional grammarCheck{}; +#endif // macOS] }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h index 109f5a1237be0a..7713c3c459977b 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h @@ -148,6 +148,15 @@ static TextInputTraits convertRawProp( sourceTraits.smartInsertDelete, defaultTraits.smartInsertDelete); +#ifdef TARGET_OS_OSX // [macOS + traits.grammarCheck = convertRawProp( + context, + rawProps, + "grammarCheck", + sourceTraits.grammarCheck, + defaultTraits.grammarCheck); +#endif // macOS] + return traits; } From d7d97d1086d98c765ed04438beedb662c7cea3de Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 13 Nov 2023 15:37:55 +0100 Subject: [PATCH 21/80] [fabric] Static cast BOOL values event data struct init for TextInput Summary: `BOOL` being a char type, when building with our current BUCK config we need to static cast it to `bool` in Obj-C++ files when assigning those to a `bool`. Test Plan: Build Zeratul with BUCK. ``` BUILDING: FINISHED IN 5m 13.7s (100%) 11302/11302 JOBS, 6/11302 UPDATED BUILD SUCCEEDED ``` Reviewers: shawndempsey Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D51258122 --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 7a00fdee53df90..899a6e7bcec96c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -461,21 +461,21 @@ - (void)textInputDidChangeSelection #if TARGET_OS_OSX // [macOS - (void)automaticSpellingCorrectionDidChange:(BOOL)enabled { if (_eventEmitter) { - std::static_pointer_cast(_eventEmitter)->onAutoCorrectChange({.enabled = enabled}); + std::static_pointer_cast(_eventEmitter)->onAutoCorrectChange({.enabled = static_cast(enabled)}); } } - (void)continuousSpellCheckingDidChange:(BOOL)enabled { if (_eventEmitter) { - std::static_pointer_cast(_eventEmitter)->onSpellCheckChange({.enabled = enabled}); + std::static_pointer_cast(_eventEmitter)->onSpellCheckChange({.enabled = static_cast(enabled)}); } } - (void)grammarCheckingDidChange:(BOOL)enabled { if (_eventEmitter) { - std::static_pointer_cast(_eventEmitter)->onGrammarCheckChange({.enabled = enabled}); + std::static_pointer_cast(_eventEmitter)->onGrammarCheckChange({.enabled = static_cast(enabled)}); } } From f84e599093be6d150fa26d5eee677e53fae70067 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Wed, 1 Nov 2023 17:16:39 -0700 Subject: [PATCH 22/80] [fabric] Selection range should use the correct backed input range Summary: **Context** - Text selection for Fabric should match Paper **Change** - Use correct position for selection range - Maintain cursor position with selection Test Plan: https://pxl.cl/3NW99 Reviewers: lefever, #rn-desktop Reviewed By: lefever Differential Revision: https://phabricator.intern.facebook.com/D51282825 --- .../TextInput/RCTTextInputComponentView.mm | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 899a6e7bcec96c..45e845a8bd775d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -584,7 +584,11 @@ - (void)setTextAndSelection:(NSInteger)eventCount UITextRange *range = [_backedTextInputView textRangeFromPosition:startPosition toPosition:endPosition]; [_backedTextInputView setSelectedTextRange:range notifyDelegate:NO]; } -#endif // [macOS] +#else // [macOS + NSInteger startPosition = MIN(start, end); + NSInteger endPosition = MAX(start, end); + [_backedTextInputView setSelectedTextRange:NSMakeRange(startPosition, endPosition - startPosition) notifyDelegate:YES]; +#endif // macOS] _comingFromJS = NO; } @@ -701,8 +705,8 @@ - (void)_updateState toPosition:selectedTextRange.end]; return AttributedString::Range{(int)start, (int)(end - start)}; #else // [macOS - // [Fabric] Placeholder till we implement selection in Fabric - return AttributedString::Range({0, 1}); + NSRange selectedTextRange = [_backedTextInputView selectedTextRange]; + return AttributedString::Range{(int)selectedTextRange.location, (int)selectedTextRange.length}; #endif // macOS] } @@ -728,8 +732,15 @@ - (void)_setAttributedString:(NSAttributedString *)attributedString } #if !TARGET_OS_OSX // [macOS] UITextRange *selectedRange = _backedTextInputView.selectedTextRange; - NSInteger oldTextLength = _backedTextInputView.attributedText.string.length; +#else + NSRange selection = [_backedTextInputView selectedTextRange]; +#endif // macOS] + NSAttributedString *oldAttributedText = [_backedTextInputView.attributedText copy]; + NSInteger oldTextLength = oldAttributedText.string.length; + _backedTextInputView.attributedText = attributedString; + +#if !TARGET_OS_OSX // [macOS] if (selectedRange.empty) { // Maintaining a cursor position relative to the end of the old text. NSInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument @@ -743,7 +754,16 @@ - (void)_setAttributedString:(NSAttributedString *)attributedString } [self _restoreTextSelection]; _lastStringStateWasUpdatedWith = attributedString; -#endif // [macOS] +#else // [macOS + if (selection.length == 0) { + // Maintaining a cursor position relative to the end of the old text. + NSInteger start = selection.location; + NSInteger offsetFromEnd = oldTextLength - start; + NSInteger newOffset = _backedTextInputView.attributedText.length - offsetFromEnd; + [_backedTextInputView setSelectedTextRange:NSMakeRange(newOffset, 0) + notifyDelegate:YES]; + } +#endif // macOS] } - (void)_setMultiline:(BOOL)multiline From c681a1d73b382c1b90596f4f6ed47e7bb144ceae Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 1 Dec 2023 04:50:54 +0100 Subject: [PATCH 23/80] [fabric][a11y] Enable accessibility property assignments for View Summary: Paper was rendering text as AXStaticText. This diff updates the `RCTParagraphComponentView` to propagate the same role in Fabric for text. This change will allow to select UI elements based on the text contents. Test Plan: Use the Accessibility Inspector in Zeratul with Fabric enabled. With the changes the text elements are presented in the a11y hierarchy with AXStaticText: {F1162808272} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51736932 Tasks: T170938725 Tags: uikit-diff --- .../ComponentViews/View/RCTViewComponentView.h | 2 +- .../ComponentViews/View/RCTViewComponentView.mm | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h index 7a903401112fa8..3d7564de46e8b1 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h @@ -56,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN * transparent in favour of some subview. * Defaults to `self`. */ -@property (nonatomic, strong, nullable, readonly) NSObject *accessibilityElement; +@property (nonatomic, strong, nullable, readonly) RCTPlatformView *accessibilityElement; // [macOS] /** * Insets used when hit testing inside this view. 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 7e205fe40bd047..928d7a8dd51faf 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -17,6 +17,7 @@ #import // [macOS] #import #import // [macOS] +#import // [macOS] #import #import #import @@ -337,10 +338,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.nativeId = RCTNSStringFromStringNilIfEmpty(newViewProps.nativeId); } -#if !TARGET_OS_OSX // [macOS] // `accessible` if (oldViewProps.accessible != newViewProps.accessible) { +#if !TARGET_OS_OSX // [macOS] self.accessibilityElement.isAccessibilityElement = newViewProps.accessible; +#else // [macOS + self.accessibilityElement.accessibilityElement = newViewProps.accessible; +#endif // macOS] } // `accessibilityLabel` @@ -356,9 +360,14 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & // `accessibilityHint` if (oldViewProps.accessibilityHint != newViewProps.accessibilityHint) { +#if !TARGET_OS_OSX // [macOS] self.accessibilityElement.accessibilityHint = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityHint); +#else // [macOS + self.accessibilityElement.accessibilityHelp = RCTNSStringFromStringNilIfEmpty(newViewProps.accessibilityHint); +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] // `accessibilityViewIsModal` if (oldViewProps.accessibilityViewIsModal != newViewProps.accessibilityViewIsModal) { self.accessibilityElement.accessibilityViewIsModal = newViewProps.accessibilityViewIsModal; @@ -390,6 +399,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (oldViewProps.accessibilityIgnoresInvertColors != newViewProps.accessibilityIgnoresInvertColors) { self.accessibilityIgnoresInvertColors = newViewProps.accessibilityIgnoresInvertColors; } +#endif // [macOS] // `accessibilityValue` if (oldViewProps.accessibilityValue != newViewProps.accessibilityValue) { @@ -408,8 +418,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.accessibilityElement.accessibilityValue = nil; } } -#endif // [macOS] - + // `testId` if (oldViewProps.testId != newViewProps.testId) { self.accessibilityIdentifier = RCTNSStringFromString(newViewProps.testId); @@ -846,7 +855,7 @@ - (void)invalidateLayer #pragma mark - Accessibility -- (NSObject *)accessibilityElement +- (RCTPlatformView *)accessibilityElement { return self; } From b2ac7c1b4f9deae1ad783c51cba763983c15f0cb Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 1 Dec 2023 04:53:33 +0100 Subject: [PATCH 24/80] [fabric][a11y] Add role mapping for common traits Summary: Paper was rendering text as AXStaticText. This diff updates the `RCTParagraphComponentView` to propagate the same role in Fabric for text. This change will allow to select UI elements based on the text contents. Test Plan: Use the Accessibility Inspector in Zeratul with Fabric enabled. With the changes the text elements are presented in the a11y hierarchy with AXStaticText: {F1162808272} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51736931 Tasks: T170938725 --- .../View/RCTViewComponentView.mm | 6 +++ .../React/Fabric/RCTConversions.h | 40 ++++++++++++++++++- 2 files changed, 45 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 928d7a8dd51faf..a8903d2125d5cc 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -377,13 +377,19 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (oldViewProps.accessibilityElementsHidden != newViewProps.accessibilityElementsHidden) { self.accessibilityElement.accessibilityElementsHidden = newViewProps.accessibilityElementsHidden; } +#endif // [macOS] // `accessibilityTraits` if (oldViewProps.accessibilityTraits != newViewProps.accessibilityTraits) { +#if !TARGET_OS_OSX // [macOS] self.accessibilityElement.accessibilityTraits = RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits); +#else // [macOS + self.accessibilityElement.accessibilityRole = RCTUIAccessibilityRoleFromAccessibilityTraits(newViewProps.accessibilityTraits); +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] // `accessibilityState` if (oldViewProps.accessibilityState != newViewProps.accessibilityState) { self.accessibilityTraits &= ~(UIAccessibilityTraitNotEnabled | UIAccessibilityTraitSelected); diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index 2fa35c998df8b9..4fb56f89939c49 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -146,7 +146,45 @@ inline UIAccessibilityTraits RCTUIAccessibilityTraitsFromAccessibilityTraits( } return result; }; -#endif // [macOS] +#else // [macOS +inline NSAccessibilityRole RCTUIAccessibilityRoleFromAccessibilityTraits( + facebook::react::AccessibilityTraits accessibilityTraits) +{ + using AccessibilityTraits = facebook::react::AccessibilityTraits; + if ((accessibilityTraits & AccessibilityTraits::Button) != AccessibilityTraits::None) { + return NSAccessibilityButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::Link) != AccessibilityTraits::None) { + return NSAccessibilityLinkRole; + } + if ((accessibilityTraits & AccessibilityTraits::Image) != AccessibilityTraits::None) { + return NSAccessibilityImageRole; + } + if ((accessibilityTraits & AccessibilityTraits::KeyboardKey) != AccessibilityTraits::None) { + return NSAccessibilityButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::StaticText) != AccessibilityTraits::None) { + return NSAccessibilityStaticTextRole; + } + if ((accessibilityTraits & AccessibilityTraits::SummaryElement) != AccessibilityTraits::None) { + return NSAccessibilityStaticTextRole; + } + if ((accessibilityTraits & AccessibilityTraits::SearchField) != AccessibilityTraits::None) { + return NSAccessibilityTextFieldRole; + } + if ((accessibilityTraits & AccessibilityTraits::Adjustable) != AccessibilityTraits::None) { + return NSAccessibilitySliderRole; + } + if ((accessibilityTraits & AccessibilityTraits::Header) != AccessibilityTraits::None) { + return NSAccessibilityStaticTextRole; + } + if ((accessibilityTraits & AccessibilityTraits::Switch) != AccessibilityTraits::None) { + return NSAccessibilityCheckBoxRole; + } + + return NSAccessibilityUnknownRole; +}; +#endif // macOS] inline CATransform3D RCTCATransform3DFromTransformMatrix(const facebook::react::Transform &transformMatrix) { From 95fddef3752ec2401c78831fe4418df1239832ae Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 1 Dec 2023 04:57:43 +0100 Subject: [PATCH 25/80] [fabric][a11y] Add role mapping for desktop specific traits Summary: Paper was rendering text as AXStaticText. This diff updates the `RCTParagraphComponentView` to propagate the same role in Fabric for text. This change will allow to select UI elements based on the text contents. Test Plan: Use the Accessibility Inspector in Zeratul with Fabric enabled. With the changes the text elements are presented in the a11y hierarchy with AXStaticText: {F1162808272} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51736933 Tasks: T170938725 --- .../React/Fabric/RCTConversions.h | 56 ++++++++++++++++++- .../components/view/AccessibilityPrimitives.h | 15 +++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index 4fb56f89939c49..09b27df970eddf 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -152,6 +152,15 @@ inline NSAccessibilityRole RCTUIAccessibilityRoleFromAccessibilityTraits( { using AccessibilityTraits = facebook::react::AccessibilityTraits; if ((accessibilityTraits & AccessibilityTraits::Button) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::Bar) != AccessibilityTraits::None) { + return NSAccessibilityToolbarRole; + } + if ((accessibilityTraits & AccessibilityTraits::PopUp) != AccessibilityTraits::None) { + return NSAccessibilityPopUpButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::Menu) != AccessibilityTraits::None) { + return NSAccessibilityMenuButtonRole; + } return NSAccessibilityButtonRole; } if ((accessibilityTraits & AccessibilityTraits::Link) != AccessibilityTraits::None) { @@ -181,7 +190,52 @@ inline NSAccessibilityRole RCTUIAccessibilityRoleFromAccessibilityTraits( if ((accessibilityTraits & AccessibilityTraits::Switch) != AccessibilityTraits::None) { return NSAccessibilityCheckBoxRole; } - + if ((accessibilityTraits & AccessibilityTraits::UpdatesFrequently) != AccessibilityTraits::None) { + return NSAccessibilityProgressIndicatorRole; + } + if ((accessibilityTraits & AccessibilityTraits::ComboBox) != AccessibilityTraits::None) { + return NSAccessibilityComboBoxRole; + } + if ((accessibilityTraits & AccessibilityTraits::Menu) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::Bar) != AccessibilityTraits::None) { + return NSAccessibilityMenuBarRole; + } + if ((accessibilityTraits & AccessibilityTraits::Item) != AccessibilityTraits::None) { + return NSAccessibilityMenuItemRole; + } + return NSAccessibilityMenuRole; + } + if ((accessibilityTraits & AccessibilityTraits::Radio) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::Group) != AccessibilityTraits::None) { + return NSAccessibilityRadioGroupRole; + } + return NSAccessibilityRadioButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::ScrollBar) != AccessibilityTraits::None) { + return NSAccessibilityScrollBarRole; + } + if ((accessibilityTraits & AccessibilityTraits::SpinButton) != AccessibilityTraits::None) { + return NSAccessibilityIncrementorRole; + } + if ((accessibilityTraits & AccessibilityTraits::Tab) != AccessibilityTraits::None) { + if ((accessibilityTraits & AccessibilityTraits::List) != AccessibilityTraits::None) { + return NSAccessibilityTabGroupRole; + } + return NSAccessibilityButtonRole; + } + if ((accessibilityTraits & AccessibilityTraits::Disclosure) != AccessibilityTraits::None) { + return NSAccessibilityDisclosureTriangleRole; + } + if ((accessibilityTraits & AccessibilityTraits::Group) != AccessibilityTraits::None) { + return NSAccessibilityGroupRole; + } + if ((accessibilityTraits & AccessibilityTraits::List) != AccessibilityTraits::None) { + return NSAccessibilityListRole; + } + if ((accessibilityTraits & AccessibilityTraits::Table) != AccessibilityTraits::None) { + return NSAccessibilityTableRole; + } + return NSAccessibilityUnknownRole; }; #endif // macOS] diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h index ce9f6eb4517e72..7fb3e909292135 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h @@ -34,6 +34,21 @@ enum class AccessibilityTraits : uint32_t { Header = (1 << 15), Switch = (1 << 16), TabBar = (1 << 17), +// [macOS + ComboBox = (1 << 18), + Menu = (1 << 19), + PopUp = (1 << 20), + Bar = (1 << 21), + Item = (1 << 22), + Group = (1 << 23), + List = (1 << 24), + Tab = (1 << 25), + Table = (1 << 26), + Disclosure = (1 << 27), + Radio = (1 << 28), + ScrollBar = (1 << 29), + SpinButton = (1 << 30), +// macOS] }; constexpr enum AccessibilityTraits operator|( From 7b11d43ae4a172c4ea684f49d5333584c5f63ef7 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 1 Dec 2023 04:57:43 +0100 Subject: [PATCH 26/80] [fabric][a11y] Make text render with static text role Summary: Paper was rendering text as AXStaticText. This diff updates the `RCTParagraphComponentView` to propagate the same role in Fabric for text. This change will allow to select UI elements based on the text contents. Test Plan: Use the Accessibility Inspector in Zeratul with Fabric enabled. With the changes the text elements are presented in the a11y hierarchy with AXStaticText: {F1162808272} Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51736954 Tasks: T170938725 --- .../Mounting/ComponentViews/Text/RCTParagraphComponentView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 62db2978f7a591..1b5b20d1b06d5a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -266,6 +266,7 @@ - (NSString *)accessibilityLabel return self.attributedText.string; } +#if !TARGET_OS_OSX // [macOS] - (BOOL)isAccessibilityElement { // All accessibility functionality of the component is implemented in `accessibilityElements` method below. @@ -274,7 +275,6 @@ - (BOOL)isAccessibilityElement return NO; } -#if !TARGET_OS_OSX // [macOS] - (NSArray *)accessibilityElements { const auto ¶graphProps = static_cast(*_props); From 1aeb0689da6c25c356c8098b3ebdf41b94af7a2b Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 7 Dec 2023 23:58:16 +0100 Subject: [PATCH 27/80] [fabric] Add wrapper class for TextView with scroll callback support Summary: The multiline text input view on macOS needs its own view hierarchy, wrapping the RCTUITextView in a scroll view to support all the features offered by the React TextInput component. This diff adds a wrapper class for RCTUITextView that provides the appropriate view hierarchy while still supporting the text input protocols required for text input. The wrapper forwards all unimplemented methods to the RCTUITextView so that it can be used as a direct substitute for the RCTUITextView. This allows us to reduce the custom changes need for macOS in RCTTextInputComponentView while re-using all the logic in RCTUITextView. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D51962394 Tasks: T167538822, T157889591 Tags: uikit-diff --- .../TextInput/Multiline/RCTWrappedTextView.h | 28 +++ .../TextInput/Multiline/RCTWrappedTextView.m | 194 ++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h create mode 100644 packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h new file mode 100644 index 00000000000000..c487c96372da17 --- /dev/null +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#if TARGET_OS_OSX // [macOS + +#import + +#import "RCTTextUIKit.h" + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTWrappedTextView : RCTPlatformView + +@property (nonatomic, weak) id textInputDelegate; +@property (assign) BOOL hideVerticalScrollIndicator; + +@end + +NS_ASSUME_NONNULL_END + +#endif // macOS] diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m new file mode 100644 index 00000000000000..6481dadf69de49 --- /dev/null +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -0,0 +1,194 @@ +/* + * 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. + */ + +#if TARGET_OS_OSX // [macOS + +#import + +#import +#import + +@implementation RCTWrappedTextView { + RCTUITextView *_forwardingTextView; + RCTUIScrollView *_scrollView; + RCTClipView *_clipView; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + self.hideVerticalScrollIndicator = NO; + + _scrollView = [[RCTUIScrollView alloc] initWithFrame:self.bounds]; + _scrollView.backgroundColor = [RCTUIColor clearColor]; + _scrollView.drawsBackground = NO; + _scrollView.borderType = NSNoBorder; + _scrollView.hasHorizontalRuler = NO; + _scrollView.hasVerticalRuler = NO; + _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [_scrollView setHasVerticalScroller:YES]; + [_scrollView setHasHorizontalScroller:NO]; + + _clipView = [[RCTClipView alloc] initWithFrame:_scrollView.bounds]; + [_scrollView setContentView:_clipView]; + + _forwardingTextView = [[RCTUITextView alloc] initWithFrame:_scrollView.bounds]; + _forwardingTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _forwardingTextView.delegate = self; + + _forwardingTextView.verticallyResizable = YES; + _forwardingTextView.horizontallyResizable = YES; + _forwardingTextView.textContainer.containerSize = NSMakeSize(FLT_MAX, FLT_MAX); + _forwardingTextView.textContainer.widthTracksTextView = YES; + _forwardingTextView.textInputDelegate = self; + + _scrollView.documentView = _forwardingTextView; + _scrollView.contentView.postsBoundsChangedNotifications = YES; + + // Enable the focus ring by default + _scrollView.enableFocusRing = YES; + [self addSubview:_scrollView]; + + // a register for those notifications on the content view. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(boundsDidChange:) + name:NSViewBoundsDidChangeNotification + object:_scrollView.contentView]; + } + + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (BOOL)isFlipped +{ + return YES; +} + +#pragma mark - +#pragma mark Method forwarding to text view + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + [invocation invokeWithTarget:_forwardingTextView]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + if ([_forwardingTextView respondsToSelector:selector]) { + return [_forwardingTextView methodSignatureForSelector:selector]; + } + + return [super methodSignatureForSelector:selector]; +} + +- (void)boundsDidChange:(NSNotification *)notification +{ +} + +#pragma mark - +#pragma mark First Responder forwarding + +- (NSResponder *)responder +{ + return _forwardingTextView; +} + +- (BOOL)acceptsFirstResponder +{ + return _forwardingTextView.acceptsFirstResponder; +} + +- (BOOL)becomeFirstResponder +{ + return [_forwardingTextView becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder +{ + return [_forwardingTextView resignFirstResponder]; +} + +#pragma mark - +#pragma mark Text Input delegate forwarding + +- (id)textInputDelegate +{ + return _forwardingTextView.textInputDelegate; +} + +- (void)setTextInputDelegate:(id)textInputDelegate +{ + _forwardingTextView.textInputDelegate = textInputDelegate; +} + +#pragma mark - +#pragma mark Scrolling control + +- (BOOL)scrollEnabled +{ + return _scrollView.isScrollEnabled; +} + +- (void)setScrollEnabled:(BOOL)scrollEnabled +{ + if (scrollEnabled) { + _scrollView.scrollEnabled = YES; + [_clipView setConstrainScrolling:NO]; + } else { + _scrollView.scrollEnabled = NO; + [_clipView setConstrainScrolling:YES]; + } +} + +- (BOOL)shouldShowVerticalScrollbar +{ + // Hide vertical scrollbar if explicity set to NO + if (self.hideVerticalScrollIndicator) { + return NO; + } + + // Hide vertical scrollbar if attributed text overflows view + CGSize textViewSize = [_forwardingTextView intrinsicContentSize]; + NSClipView *clipView = (NSClipView *)_scrollView.contentView; + if (textViewSize.height > clipView.bounds.size.height) { + return YES; + }; + + return NO; +} + +- (void)textInputDidChange +{ + [_scrollView setHasVerticalScroller:[self shouldShowVerticalScrollbar]]; +} + +- (void)setAttributedText:(NSAttributedString *)attributedText +{ + [_forwardingTextView setAttributedText:attributedText]; + [_scrollView setHasVerticalScroller:[self shouldShowVerticalScrollbar]]; +} + +#pragma mark - +#pragma mark Text Container Inset override for NSTextView + +// This method is there to match the textContainerInset property on RCTUITextField +- (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets +{ + // RCTUITextView has logic in setTextContainerInset[s] to convert th UIEdgeInsets to a valid NSSize struct + _forwardingTextView.textContainerInsets = textContainerInsets; +} + +@end + +#endif // macOS] From 6f297637cc3c08ea112b5b2f46881a22e4f5f3bb Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 8 Dec 2023 00:02:32 +0100 Subject: [PATCH 28/80] [fabric] Add responder property to backing text input view protocol Summary: Add a `responder` property to support assigning the first responder to the actual textfield/textview if the view is wrapped or not. The wrapped text view already implements this property. This diff brings the same functionality to the text field and declares it on the common protocol. Test Plan: Tested later in this stack. Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey, chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51962395 Tasks: T167538822, T157889591 --- .../Text/TextInput/RCTBackedTextInputViewProtocol.h | 1 + .../Libraries/Text/TextInput/Singleline/RCTUITextField.mm | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 8411f28839556c..266a97d19356f6 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -38,6 +38,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readonly) BOOL textWasPasted; #else // [macOS @property (nonatomic, assign) BOOL textWasPasted; +@property (nonatomic, readonly) NSResponder *responder; #endif // macOS] @property (nonatomic, assign, readonly) BOOL dictationRecognizing; @property (nonatomic, assign) UIEdgeInsets textContainerInset; diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm index b85551c58b7ab0..45555d743298ef 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm @@ -196,6 +196,11 @@ - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset #if TARGET_OS_OSX // [macOS +- (NSResponder *)responder +{ + return self; +} + + (Class)cellClass { return RCTUITextFieldCell.class; From e017f32bbbdcd9bf0f5e642b3aea9119a3625cbe Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 8 Dec 2023 00:05:42 +0100 Subject: [PATCH 29/80] [fabric] Update backed text input copy method to support view instances Summary: We're using wrapped text views with method forwarding, which enables a view to have fully supported text input interfaces. The text input copy helper method signature was limiting its use to RCTUITextView. To support wrapped text views the typing was changed to RCTPlatformView. All properties used in the implementation of the copy method are declared on RCTBackedTextInputViewProtocol. Test Plan: Tested later in this stack. Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey, chpurrer Differential Revision: https://phabricator.intern.facebook.com/D51962397 Tasks: T167538822, T157889591 --- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.h | 4 ++-- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h index a7592cca95469d..337ee6deb19a75 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h @@ -22,8 +22,8 @@ void RCTCopyBackedTextInput( RCTUIView *fromTextInput, RCTUIView *toTextInput #else // [macOS - RCTUITextView *fromTextInput, - RCTUITextView *toTextInput + RCTPlatformView *fromTextInput, + RCTPlatformView *toTextInput #endif // macOS] ); diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 67fb26dc206748..d2e6415ae6f597 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -23,8 +23,8 @@ void RCTCopyBackedTextInput( RCTUIView *fromTextInput, RCTUIView *toTextInput #else // [macOS - RCTUITextView *fromTextInput, - RCTUITextView *toTextInput + RCTPlatformView *fromTextInput, + RCTPlatformView *toTextInput #endif // macOS] ) { From 3c8cdb6ed5bef02e178a35706abc49e0f263dc4a Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 8 Dec 2023 00:12:33 +0100 Subject: [PATCH 30/80] [fabric] Use wrapped text view for multiline TextInput Summary: This diff updates the core TextInput RN component to use the wrapped text view for multiline TextInput. Switching to `RCTWrappedTextView` enables correct `borderWidth` and `contentInsets` support for multi line text inputs while maintaining the same functionality for single line text input. Scrolling text views are also supported correctly, with vertical height dependent scrollers. Test Plan: - Build Zeratul with Fabric enabled. - Type in the search field to test the layout of the text contents - Type in the composer to test multi line support and the layout of the text contents | Before | After | |--| | https://pxl.cl/3Xrrx | https://pxl.cl/3Xrr9 | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D51962396 Tasks: T167538822, T157889591 --- .../TextInput/RCTTextInputComponentView.mm | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 45e845a8bd775d..e97ee4bb659960 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -15,6 +15,9 @@ #import #import #import +#if TARGET_OS_OSX // [macOS +#import +#endif // macOS] #import "RCTConversions.h" #import "RCTTextInputNativeCommands.h" @@ -70,7 +73,11 @@ - (instancetype)initWithFrame:(CGRect)frame _props = defaultProps; auto &props = *defaultProps; +#if !TARGET_OS_OSX // [macOS] _backedTextInputView = props.traits.multiline ? [RCTUITextView new] : [RCTUITextField new]; +#else // [macOS + _backedTextInputView = props.traits.multiline ? [[RCTWrappedTextView alloc] initWithFrame:self.bounds] : [RCTUITextField new]; +#endif // macOS] _backedTextInputView.textInputDelegate = self; _ignoreNextTextInputCall = NO; _comingFromJS = NO; @@ -540,7 +547,7 @@ - (void)focus #else // [macOS NSWindow *window = _backedTextInputView.window; if (window) { - [window makeFirstResponder:_backedTextInputView]; + [window makeFirstResponder:_backedTextInputView.responder]; } #endif // macOS] } @@ -551,7 +558,7 @@ - (void)blur [_backedTextInputView resignFirstResponder]; #else NSWindow *window = _backedTextInputView.window; - if (window && window.firstResponder == _backedTextInputView) { + if (window && window.firstResponder == _backedTextInputView.responder) { // Calling makeFirstResponder with nil will call resignFirstResponder and make the window the first responder [window makeFirstResponder:nil]; } @@ -772,7 +779,7 @@ - (void)_setMultiline:(BOOL)multiline #if !TARGET_OS_OSX // [macOS] RCTUIView *backedTextInputView = multiline ? [RCTUITextView new] : [RCTUITextField new]; #else // [macOS - RCTUITextView *backedTextInputView = [RCTUITextView new]; + RCTPlatformView *backedTextInputView = multiline ? [RCTWrappedTextView new] : [RCTUITextField new]; #endif // macOS] backedTextInputView.frame = _backedTextInputView.frame; RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView); From 94e40900cc2595e1a68d6a9cdec7b399c3e76cd3 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sat, 9 Dec 2023 04:42:29 +0100 Subject: [PATCH 31/80] [fabric] Support showing/hiding the focus ring for TextInput Summary: This diff propagates the View `enableFocusRing` property to the backing textfield/textview to correctly set up the NSTextField/NSScrollView with the latest provided property value for single and multiline TextInput components. Test Plan: - Run Zeratul with Fabric enabled - Focus the composer in a message thread - The blue focus ring should not show up around the composer | Before | After | |--| | https://pxl.cl/3XHP8 | {F1169320851} | Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D52005858 Tasks: T167538822 --- .../Text/TextInput/Multiline/RCTWrappedTextView.m | 13 +++++++++++++ .../Text/TextInput/RCTBackedTextInputViewProtocol.h | 1 + .../TextInput/RCTTextInputComponentView.mm | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m index 6481dadf69de49..43630018e62fe5 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -189,6 +189,19 @@ - (void)setTextContainerInset:(UIEdgeInsets)textContainerInsets _forwardingTextView.textContainerInsets = textContainerInsets; } +#pragma mark - +#pragma mark Focus ring + +- (BOOL)enableFocusRing +{ + return _scrollView.enableFocusRing; +} + +- (void)setEnableFocusRing:(BOOL)enableFocusRing +{ + _scrollView.enableFocusRing = enableFocusRing; +} + @end #endif // macOS] diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index 266a97d19356f6..ad80d3cc4b1d2d 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN #else // [macOS @property (nonatomic, assign) BOOL textWasPasted; @property (nonatomic, readonly) NSResponder *responder; +@property (nonatomic, assign) BOOL enableFocusRing; #endif // macOS] @property (nonatomic, assign, readonly) BOOL dictationRecognizing; @property (nonatomic, assign) UIEdgeInsets textContainerInset; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index e97ee4bb659960..83526e7aac0576 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -466,6 +466,13 @@ - (void)textInputDidChangeSelection } #if TARGET_OS_OSX // [macOS +- (void)setEnableFocusRing:(BOOL)enableFocusRing { + [super setEnableFocusRing:enableFocusRing]; + if ([_backedTextInputView respondsToSelector:@selector(setEnableFocusRing:)]) { + [_backedTextInputView setEnableFocusRing:enableFocusRing]; + } +} + - (void)automaticSpellingCorrectionDidChange:(BOOL)enabled { if (_eventEmitter) { std::static_pointer_cast(_eventEmitter)->onAutoCorrectChange({.enabled = static_cast(enabled)}); From 4c79515ec83217d5b984a474392cb81d66b64885 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sat, 9 Dec 2023 04:46:56 +0100 Subject: [PATCH 32/80] [fabric] Implement escape/cancel key press callback for TextInput Summary: Pressing the escape key down in a TextInput on macOS will be captured on the native side as a cancel event. This diff adds support for sending the "Escape" key as a key pressed event and wires the delegate method of the backing text input to call the key press event handler with the correct payload. Test Plan: - Run Zeratul with Fabric enabled - Add a key press handler on the textfield logging the received data (e.g. on MDSTextInput) - Focus the textfield - Press the escape key - The key press handler should be called and print that the "Escape" key was pressed. https://pxl.cl/3XHR2 Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52005857 Tasks: T167538822 --- .../TextInput/RCTTextInputComponentView.mm | 19 ++++++++++++++++++- .../iostextinput/TextInputEventEmitter.cpp | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 83526e7aac0576..b10240b489048c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -499,7 +499,24 @@ - (BOOL)hasValidKeyDownOrValidKeyUp:(nonnull NSString *)key { - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event {} -- (void)textInputDidCancel {} +- (void)textInputDidCancel +{ + if (_eventEmitter) { + KeyPressMetrics keyPressMetrics; + keyPressMetrics.text = RCTStringFromNSString(@"\x1B"); // Escape key + keyPressMetrics.eventCount = _mostRecentEventCount; + + auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); + auto const &props = *std::static_pointer_cast(_props); + if (props.onKeyPressSync) { + textInputEventEmitter.onKeyPressSync(keyPressMetrics); + } else { + textInputEventEmitter.onKeyPress(keyPressMetrics); + } + } + + [self textInputDidEndEditing]; +} - (NSDragOperation)textInputDraggingEntered:(nonnull id)draggingInfo { return NSDragOperationNone; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp index 7fec0bb7c23486..d5185ae72f659e 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp @@ -67,6 +67,8 @@ static jsi::Value keyPressMetricsPayload( key = "Enter"; } else if (keyPressMetrics.text.front() == '\t') { key = "Tab"; + } else if (keyPressMetrics.text.front() == '\x1B') { + key = "Escape"; } else { key = keyPressMetrics.text.front(); } From 042fb076eb897583da52f614cf1b2379ff3ad227 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sat, 9 Dec 2023 04:52:23 +0100 Subject: [PATCH 33/80] [fabric] Submit scroll view metrics when scrolling multiline TextInput Summary: This diff implements the scrollViewDidScroll delegate method call on the mutli line text input (`RCTWrappedTextView`). The text metrics are updated with the right values read directly from the emitting scroll view so that the TextInput would submit the right payload for scrolling multi line TextInput components at all times. Test Plan: - Run Zeratul with Fabric enabled - Log the onScroll callback data for TextInput used by the composer (e.g. MDSTextInput) - Type a message spanning over multiple lines in the composer until a scroll bar is visible - Scroll the composer contents - The logging shows the text metrics provided as payload for the onScroll handler. https://pxl.cl/3XHRl Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D52005859 Tasks: T167538822 --- .../TextInput/Multiline/RCTWrappedTextView.m | 14 +++++++++++++ .../RCTBackedTextInputDelegateAdapter.mm | 6 +++--- .../TextInput/RCTTextInputComponentView.mm | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m index 43630018e62fe5..cef137859ab75c 100644 --- a/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m +++ b/packages/react-native/Libraries/Text/TextInput/Multiline/RCTWrappedTextView.m @@ -56,10 +56,17 @@ - (instancetype)initWithFrame:(CGRect)frame [self addSubview:_scrollView]; // a register for those notifications on the content view. + #if !TARGET_OS_OSX // [macOS] [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChange:) name:NSViewBoundsDidChangeNotification object:_scrollView.contentView]; + #else // [macOS + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(scrollViewDidScroll:) + name:NSViewBoundsDidChangeNotification + object:_scrollView.contentView]; + #endif // macOS] } return self; @@ -135,6 +142,13 @@ - (void)setTextInputDelegate:(id)textInputDelegate #pragma mark - #pragma mark Scrolling control +#if TARGET_OS_OSX // [macOS +- (void)scrollViewDidScroll:(NSNotification *)notification +{ + [self.textInputDelegate scrollViewDidScroll:_scrollView]; +} +#endif // macOS] + - (BOOL)scrollEnabled { return _scrollView.isScrollEnabled; diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm index aab2d70e9e7650..5f50c67472dadb 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm @@ -391,17 +391,17 @@ - (void)textViewDidChangeSelection:(__unused UITextView *)textView [self textViewProbablyDidChangeSelection]; } +#endif // [macOS] + #pragma mark - UIScrollViewDelegate -- (void)scrollViewDidScroll:(UIScrollView *)scrollView +- (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS] { if ([_backedTextInputView.textInputDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { [_backedTextInputView.textInputDelegate scrollViewDidScroll:scrollView]; } } -#endif // [macOS] - #if TARGET_OS_OSX // [macOS #pragma mark - NSTextViewDelegate diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index b10240b489048c..600e8b04e34626 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -553,7 +553,28 @@ - (BOOL)textInputShouldHandlePaste:(nonnull id)s - (void)scrollViewDidScroll:(RCTUIScrollView *)scrollView // [macOS] { if (_eventEmitter) { +#if !TARGET_OS_OSX // [macOS] static_cast(*_eventEmitter).onScroll([self _textInputMetrics]); +#else // [macOS + TextInputMetrics metrics = [self _textInputMetrics]; // [macOS] + + CGPoint contentOffset = scrollView.contentOffset; + metrics.contentOffset = {contentOffset.x, contentOffset.y}; + + UIEdgeInsets contentInset = scrollView.contentInset; + metrics.contentInset = {contentInset.left, contentInset.top, contentInset.right, contentInset.bottom}; + + CGSize contentSize = scrollView.contentSize; + metrics.contentSize = {contentSize.width, contentSize.height}; + + CGSize layoutMeasurement = scrollView.bounds.size; + metrics.layoutMeasurement = {layoutMeasurement.width, layoutMeasurement.height}; + + CGFloat zoomScale = scrollView.zoomScale ?: 1; + metrics.zoomScale = zoomScale; + + static_cast(*_eventEmitter).onScroll(metrics); +#endif // macOS] } } From ea1810fce642c2b2020b22d290651fb3b32371fa Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 15 Jan 2024 20:23:58 +0100 Subject: [PATCH 34/80] [fabric] Fix random TextInput cursor position changes while typing Summary: When text completions or special character symbols are displayed ahead of time, the content is present in the native text view attributed string. If a state update is applied, the cursor position will not be read as being at the end of the string while typing, which will shift the cursor position after the update. This diff disables native text view content updates while temporary context is displayed to fix the wrong cursor position updates. Test Plan: Run Zeratul with Fabric and type text in the message composer until macOS renders a text completion/suggestion. Keep typing and see if the cursor stays at the end of the string. | Before | After | |--| | https://pxl.cl/49PW4 | https://pxl.cl/49PWh | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52787094 Tasks: T174286406 --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 600e8b04e34626..eb2cbf1844b14a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -779,6 +779,13 @@ - (void)_restoreTextSelection - (void)_setAttributedString:(NSAttributedString *)attributedString { +#if TARGET_OS_OSX // [macOS + // When the text view displays temporary content (e.g. completions, accents), do not update the attributed string. + if (_backedTextInputView.hasMarkedText) { + return; + } +#endif // macOS] + if ([self _textOf:attributedString equals:_backedTextInputView.attributedText]) { return; } From 6af8d4df33ddcbf69de2225bf6fa56ccf363d522 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 18 Jan 2024 03:10:00 +0100 Subject: [PATCH 35/80] [fabric] Add submitKeyEvents property to TextInput Summary: This diff adds the macOS-only property to the TextInput component called `submitKeyEvents` which allows the user to provide key combinations that will trigger a submit action. This is being used to implement message sending on hitting the return key. Test Plan: Tested later in this stack Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Subscribers: taskcreeper Differential Revision: https://phabricator.intern.facebook.com/D52860413 --- .../components/iostextinput/primitives.h | 19 ++++++++ .../iostextinput/propsConversions.h | 45 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h index 2057764b8b9ed7..722868e695a750 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h @@ -89,6 +89,18 @@ class Selection final { int end{0}; }; +#if TARGET_OS_OSX // [macOS +class SubmitKeyEvent final { + public: + std::string key{}; + bool altKey{false}; + bool shiftKey{false}; + bool ctrlKey{false}; + bool metaKey{false}; + bool functionKey{false}; +}; +#endif // macOS] + /* * Controls features of text inputs. */ @@ -239,6 +251,13 @@ class TextInputTraits final { * Default value: `empty` (`null`). */ std::optional grammarCheck{}; + + /* + * List of key combinations that should submit. + * macOS + * Default value: `empty` applies as 'Enter' key. + */ + std::vector submitKeyEvents{}; #endif // macOS] }; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h index 7713c3c459977b..d593177f5cb848 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h @@ -192,5 +192,50 @@ inline void fromRawValue( } else { LOG(ERROR) << "Unsupported Selection type"; } + +#if TARGET_OS_OSX // [macOS +static inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + SubmitKeyEvent &result) { + auto map = (std::unordered_map)value; + + auto tmp_key = map.find("key"); + if (tmp_key != map.end()) { + fromRawValue(context, tmp_key->second, result.key); + } + auto tmp_altKey = map.find("altKey"); + if (tmp_altKey != map.end()) { + fromRawValue(context, tmp_altKey->second, result.altKey); + } + auto tmp_shiftKey = map.find("shiftKey"); + if (tmp_shiftKey != map.end()) { + fromRawValue(context, tmp_shiftKey->second, result.shiftKey); + } + auto tmp_ctrlKey = map.find("ctrlKey"); + if (tmp_ctrlKey != map.end()) { + fromRawValue(context, tmp_ctrlKey->second, result.ctrlKey); + } + auto tmp_metaKey = map.find("metaKey"); + if (tmp_metaKey != map.end()) { + fromRawValue(context, tmp_metaKey->second, result.metaKey); + } + auto tmp_functionKey = map.find("functionKey"); + if (tmp_functionKey != map.end()) { + fromRawValue(context, tmp_functionKey->second, result.functionKey); + } +} + +static inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + SubmitKeyEvent newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } } +#endif // macOS] } // namespace facebook::react From ea8abc6b19080d79e81a4c548b6015204beea2c7 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 18 Jan 2024 03:11:42 +0100 Subject: [PATCH 36/80] [fabric] Implement TextInput key down event checking for submit Summary: This diff implements the key down event processing to check if any of the provided submit key combinations were entered in the TextInput. If a match is found, the submit callback is triggered. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52860412 --- .../TextInput/RCTTextInputComponentView.mm | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index eb2cbf1844b14a..a9ff4abdb54f70 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -17,6 +17,7 @@ #import #if TARGET_OS_OSX // [macOS #import +#import #endif // macOS] #import "RCTConversions.h" @@ -497,7 +498,44 @@ - (BOOL)hasValidKeyDownOrValidKeyUp:(nonnull NSString *)key { return YES; } -- (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event {} +- (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event +{ + BOOL shouldSubmit = NO; + NSDictionary *keyEvent = [RCTViewKeyboardEvent bodyFromEvent:event]; + auto const &props = *std::static_pointer_cast(_props); + if (props.traits.submitKeyEvents.empty()) { + shouldSubmit = [keyEvent[@"key"] isEqualToString:@"Enter"] + && ![keyEvent[@"altKey"] boolValue] + && ![keyEvent[@"shiftKey"] boolValue] + && ![keyEvent[@"ctrlKey"] boolValue] + && ![keyEvent[@"metaKey"] boolValue] + && ![keyEvent[@"functionKey"] boolValue]; // Default clearTextOnSubmit key + } else { + NSString *keyValue = keyEvent[@"key"]; + NSUInteger keyValueLength = [keyValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + std::string key = std::string([keyValue UTF8String], keyValueLength); + for (auto const &submitKeyEvent : props.traits.submitKeyEvents) { + if ( + submitKeyEvent.key == key && + submitKeyEvent.altKey == [keyEvent[@"altKey"] boolValue] && + submitKeyEvent.shiftKey == [keyEvent[@"shiftKey"] boolValue] && + submitKeyEvent.ctrlKey == [keyEvent[@"ctrlKey"] boolValue] && + submitKeyEvent.metaKey == [keyEvent[@"metaKey"] boolValue] && + submitKeyEvent.functionKey == [keyEvent[@"functionKey"] boolValue] + ) { + shouldSubmit = YES; + break; + } + } + } + + if (shouldSubmit) { + if (_eventEmitter) { + auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); + textInputEventEmitter.onSubmitEditing([self _textInputMetrics]); + } + } +} - (void)textInputDidCancel { From d7434f2279ececcc58597a0f5fa558f44495e79b Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 18 Jan 2024 03:24:57 +0100 Subject: [PATCH 37/80] [fabric] Add support for clearing the TextInput on submit Summary: This diff adds the macOS-only `clearTextOnSubmit` property for the TextInput component. When set to `true`, the TextInput will clear its content after a submit was triggered through the configured `submitKeyEvents` key combinations. Test Plan: - Run Zeratul with Fabric enabled and enter a message in the composer. - Submit the message by pressing the `Return` key. - Check that `Shift + Return` still works without submitting. https://pxl.cl/4bw3D Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: shawndempsey, chpurrer Differential Revision: https://phabricator.intern.facebook.com/D52860414 --- .../TextInput/RCTTextInputComponentView.mm | 5 +++++ .../renderer/components/iostextinput/primitives.h | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index a9ff4abdb54f70..d7dc30754c1a3c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -534,6 +534,11 @@ - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); textInputEventEmitter.onSubmitEditing([self _textInputMetrics]); } + + if (props.traits.clearTextOnSubmit) { + _backedTextInputView.attributedText = nil; + [self textInputDidChange]; + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h index 722868e695a750..de4e6f3d00648d 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h @@ -247,17 +247,24 @@ class TextInputTraits final { #ifdef TARGET_OS_OSX // [macOS /* * Can be empty (`null` in JavaScript) which means `default`. - * maOS + * macOS * Default value: `empty` (`null`). */ std::optional grammarCheck{}; /* * List of key combinations that should submit. - * macOS - * Default value: `empty` applies as 'Enter' key. + * macOS-only + * Default value: `empty list` applies as 'Enter' key. */ std::vector submitKeyEvents{}; + + /* + * When set to `true`, the text will be cleared after the submit. + * macOS-only + * Default value: `false` + */ + bool clearTextOnSubmit{false}; #endif // macOS] }; From 64cd1664bb39ad9bce4ac6793f582688cb5bcb6c Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 19 Jan 2024 20:40:51 +0100 Subject: [PATCH 38/80] [fabric] Add support for the secure text entry to TextInput Summary: This diff adds support for the secure text entry mode of the single line TextInput component. The backing text field is switched for an extension of the NSSecureTextField on macOS. This was implemented on the single line text input implementation in Paper. This logic is now combined with the multi line text input on the RCTTextInputComponentView on Fabric. The secure text field uses a macro define to re-include the text field source files with minor changes, this to avoid code duplication. To support the inclusion magic happening here, the `import` statement of the text field header had to be converted to an `include` so that the macro-toggling-magic of the secure text field would work. Test Plan: - Run Zeratul with Fabric enabled and use the auth screen of Messenger to test the secure text entry with the password field. https://pxl.cl/4c2CD Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52924481 --- .../TextInput/RCTTextInputComponentView.mm | 34 ++++++++++++++++++- .../TextInput/RCTTextInputUtils.mm | 4 +++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index d7dc30754c1a3c..2d2e4bc45d9d09 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -12,7 +12,14 @@ #import #import + +#if !TARGET_OS_OSX // [macOS] #import +#else // [macOS +#include +#include +#endif // macOS] + #import #import #if TARGET_OS_OSX // [macOS @@ -203,11 +210,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _backedTextInputView.scrollEnabled = newTextInputProps.traits.scrollEnabled; } -#if !TARGET_OS_OSX // [macOS] if (newTextInputProps.traits.secureTextEntry != oldTextInputProps.traits.secureTextEntry) { +#if !TARGET_OS_OSX // [macOS] _backedTextInputView.secureTextEntry = newTextInputProps.traits.secureTextEntry; +#else // [macOS + [self _setSecureTextEntry:newTextInputProps.traits.secureTextEntry]; +#endif // macOS] } +#if !TARGET_OS_OSX // [macOS] if (newTextInputProps.traits.keyboardType != oldTextInputProps.traits.keyboardType) { _backedTextInputView.keyboardType = RCTUIKeyboardTypeFromKeyboardType(newTextInputProps.traits.keyboardType); } @@ -882,6 +893,27 @@ - (void)_setMultiline:(BOOL)multiline [self addSubview:_backedTextInputView]; } +#if TARGET_OS_OSX // [macOS +- (void)_setSecureTextEntry:(BOOL)secureTextEntry +{ + [_backedTextInputView removeFromSuperview]; + RCTPlatformView *backedTextInputView = secureTextEntry ? [RCTUISecureTextField new] : [RCTUITextField new]; + backedTextInputView.frame = _backedTextInputView.frame; + RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView); + + // Copy the text field specific properties if we came from a single line input before the switch + if ([_backedTextInputView isKindOfClass:[RCTUITextField class]]) { + RCTUITextField *previousTextField = (RCTUITextField *)_backedTextInputView; + RCTUITextField *newTextField = (RCTUITextField *)backedTextInputView; + newTextField.textAlignment = previousTextField.textAlignment; + newTextField.text = previousTextField.text; + } + + _backedTextInputView = backedTextInputView; + [self addSubview:_backedTextInputView]; +} +#endif // macOS] + - (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText { // When the dictation is running we can't update the attributed text on the backed up text view diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index d2e6415ae6f597..85e8830cda21d9 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -32,6 +32,10 @@ void RCTCopyBackedTextInput( toTextInput.placeholder = fromTextInput.placeholder; toTextInput.placeholderColor = fromTextInput.placeholderColor; toTextInput.textContainerInset = fromTextInput.textContainerInset; + +#if TARGET_OS_OSX // [macOS + toTextInput.autoresizingMask = fromTextInput.autoresizingMask; +#endif // macOS] #if TARGET_OS_IOS // [macOS] [visionOS] toTextInput.inputAccessoryView = fromTextInput.inputAccessoryView; #endif // [macOS] [visionOS] From 737ac48def97fca8e080178f00938fa68a847a9c Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 19 Jan 2024 20:43:51 +0100 Subject: [PATCH 39/80] [fabric] Copy accessibility attributes when switching TextInput backing view Summary: This diff adds the copy of the a11y attributes when switching the TextInput to the secure text entry which triggers the switch to a NSSecureTextField and runs the copy of the NSTextField attributes to the new backing text field. Test Plan: - Run Zeratul with Fabric enabled and inspect the Messenger password field on the auth screen using the Accessability Inspector to check that the attributes are passed down to the native text field. {F1332886925} Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D52924480 --- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 85e8830cda21d9..9ad02214e9fea9 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -34,6 +34,11 @@ void RCTCopyBackedTextInput( toTextInput.textContainerInset = fromTextInput.textContainerInset; #if TARGET_OS_OSX // [macOS + toTextInput.accessibilityElement = fromTextInput.accessibilityElement; + toTextInput.accessibilityHelp = fromTextInput.accessibilityHelp; + toTextInput.accessibilityIdentifier = fromTextInput.accessibilityIdentifier; + toTextInput.accessibilityLabel = fromTextInput.accessibilityLabel; + toTextInput.accessibilityRole = fromTextInput.accessibilityRole; toTextInput.autoresizingMask = fromTextInput.autoresizingMask; #endif // macOS] #if TARGET_OS_IOS // [macOS] [visionOS] From 3ef4f80b68634bbcea38cc628aecd39ca6b7b4a1 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 29 Jan 2024 20:11:12 +0100 Subject: [PATCH 40/80] [fabric] Add HostPlatformViewProps to ViewProps Summary: This diff updates `ViewProps` with a reference to the `HostPlatformViewProps`, allowing to move macOS specific properties in a separate class without having to change `ViewProps`. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53241732 Tasks: T157889406, T154618477 --- .../components/view/BaseViewProps.cpp | 2 ++ .../renderer/components/view/BaseViewProps.h | 4 ++- .../components/view/HostPlatformViewProps.h | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index fdd336626bf26e..60a6e92ce73ab0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -54,6 +54,7 @@ BaseViewProps::BaseViewProps( const RawProps& rawProps) : YogaStylableProps(context, sourceProps, rawProps), AccessibilityProps(context, sourceProps, rawProps), + HostPlatformViewProps(context, sourceProps, rawProps, shouldSetRawProps), opacity( CoreFeatures::enablePropIteratorSetter ? sourceProps.opacity : convertRawProp( @@ -292,6 +293,7 @@ void BaseViewProps::setProp( // reuse the same values. YogaStylableProps::setProp(context, hash, propName, value); AccessibilityProps::setProp(context, hash, propName, value); + HostPlatformViewProps::setProp(context, hash, propName, value); static auto defaults = BaseViewProps{}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index 203cb0a6430d31..1749ec43601ad3 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -20,8 +21,9 @@ namespace facebook::react { -class BaseViewProps : public YogaStylableProps, public AccessibilityProps { +class BaseViewProps : public YogaStylableProps, public AccessibilityProps, public HostPlatformViewProps { public: + using SharedViewProps = std::shared_ptr; BaseViewProps() = default; BaseViewProps( const PropsParserContext& context, diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h new file mode 100644 index 00000000000000..c010d69b562ce9 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace facebook::react { +class HostPlatformViewProps { + public: + HostPlatformViewProps() = default; + HostPlatformViewProps( + const PropsParserContext &context, + const HostPlatformViewProps &sourceProps, + const RawProps &rawProps, + bool shouldSetRawProps = true) {} + + void + setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value) {} +}; +} // namespace facebook::react From 3d5deb810638e676d0f59f49f01dadad254b3861 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 29 Jan 2024 20:11:39 +0100 Subject: [PATCH 41/80] [fabric] Add HostPlatformViewEventEmitter to ViewEventEmitter Summary: This diff updates the `ViewEventEmitter` with a reference to the `HostPlatformViewEventEmitter`, allowing to move macOS specific event emitters in a separate file without having to change `ViewEventEmitter`. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53241735 Tasks: T157889406, T154618477 --- .../components/view/BaseViewEventEmitter.h | 7 ++++--- .../components/view/HostPlatformViewEventEmitter.h | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h index 584bc01239ce44..1bd62d95a09703 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h @@ -13,13 +13,14 @@ #include #include -#include "TouchEventEmitter.h" +#include "HostPlatformViewEventEmitter.h" namespace facebook::react { -class BaseViewEventEmitter : public TouchEventEmitter { +class BaseViewEventEmitter : public HostPlatformViewEventEmitter { public: - using TouchEventEmitter::TouchEventEmitter; + using HostPlatformViewEventEmitter::HostPlatformViewEventEmitter; + #pragma mark - Accessibility diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h new file mode 100644 index 00000000000000..6f0924885fda6d --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h @@ -0,0 +1,14 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { + using HostPlatformViewEventEmitter = TouchEventEmitter; +} // namespace facebook::react From 950074ed339dabbeb5fecf617877b3030ad78dce Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 29 Jan 2024 20:35:55 +0100 Subject: [PATCH 42/80] [fabric] Move focus props to HostPlatformViewProps Summary: Moving the macOS specific props from ViewProps to the macOS specific HostPlatformViewProps extension. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53241734 Tasks: T157889406, T154618477 --- .../components/view/macOS/MacOSViewProps.cpp | 40 +++++++++++++++++++ .../components/view/macOS/MacOSViewProps.h | 35 ++++++++++++++++ .../components/view/BaseViewProps.cpp | 25 ------------ .../renderer/components/view/BaseViewProps.h | 5 --- .../components/view/HostPlatformViewProps.h | 8 ++++ 5 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp create mode 100644 ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp new file mode 100644 index 00000000000000..b39cf09d57af93 --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include "MacOSViewProps.h" + +#include +#include + +namespace facebook::react { + +MacOSViewProps::MacOSViewProps( + const PropsParserContext &context, + const MacOSViewProps &sourceProps, + const RawProps &rawProps, + bool shouldSetRawProps) + : focusable( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.focusable + : convertRawProp(context, rawProps, "focusable", sourceProps.focusable, {})), + enableFocusRing( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.enableFocusRing + : convertRawProp(context, rawProps, "enableFocusRing", sourceProps.enableFocusRing, true)); + +void MacOSViewProps::setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value) { + switch (hash) { + RAW_SET_PROP_SWITCH_CASE_BASIC(focusable, false); + RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing, true); + } +} + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h new file mode 100644 index 00000000000000..45a2bf81d4c8fc --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace facebook::react { + +class MacOSViewProps { + public: + MacOSViewProps() = default; + MacOSViewProps( + const PropsParserContext &context, + const MacOSViewProps &sourceProps, + const RawProps &rawProps, + bool shouldSetRawProps = true); + + void + setProp( + const PropsParserContext &context, + RawPropsPropNameHash hash, + const char *propName, + RawValue const &value); + + bool focusable{false}; + bool enableFocusRing{true}; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index 60a6e92ce73ab0..e14d0d82abec2a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -240,27 +240,6 @@ BaseViewProps::BaseViewProps( sourceProps.removeClippedSubviews, false)), -#ifdef TARGET_OS_OSX // [macOS - focusable( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.focusable - : convertRawProp( - context, - rawProps, - "focusable", - sourceProps.focusable, - false)), - enableFocusRing( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.enableFocusRing - : convertRawProp( - context, - rawProps, - "enableFocusRing", - sourceProps.enableFocusRing, - false)), -#endif // macOS] - experimental_layoutConformance( CoreFeatures::enablePropIteratorSetter ? sourceProps.experimental_layoutConformance @@ -314,10 +293,6 @@ void BaseViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(collapsable); RAW_SET_PROP_SWITCH_CASE_BASIC(removeClippedSubviews); RAW_SET_PROP_SWITCH_CASE_BASIC(experimental_layoutConformance); -#ifdef TARGET_OS_OSX // [macOS - RAW_SET_PROP_SWITCH_CASE_BASIC(focusable); - RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing); -#endif // macOS] // events field VIEW_EVENT_CASE(PointerEnter); VIEW_EVENT_CASE(PointerEnterCapture); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index 1749ec43601ad3..995c455b5e784b 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -76,11 +76,6 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps, publi LayoutConformance experimental_layoutConformance{}; -#ifdef TARGET_OS_OSX // [macOS - bool focusable{false}; - bool enableFocusRing{false}; -#endif // macOS] - #pragma mark - Convenience Methods BorderMetrics resolveBorderMetrics(const LayoutMetrics& layoutMetrics) const; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h index c010d69b562ce9..36f00723ffcf30 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h @@ -7,10 +7,15 @@ #pragma once +#if !TARGET_OS_OSX // [macOS #include #include +#else // [macOS +#include +#endif // macOS] namespace facebook::react { +#if !TARGET_OS_OSX // [macOS] class HostPlatformViewProps { public: HostPlatformViewProps() = default; @@ -27,4 +32,7 @@ class HostPlatformViewProps { const char *propName, RawValue const &value) {} }; +#else // [macOS + using HostPlatformViewProps = MacOSViewProps; +#endif // macOS] } // namespace facebook::react From 49b4cce340fe192cc3ddb33da7f2cc67d9a07153 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 30 Jan 2024 17:26:11 +0100 Subject: [PATCH 43/80] [fabric] Add valid key down/up props to View Summary: This diff adds the `validKeyDown` and `validKeyUp` props to the View component to provide a list of key event matchers used to filter out key event that have to be handled manually by the JS side. These properties were added through the HostPlatformViewProps class to keep the macOS specific properties in a separate file from the iOS ViewProps. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53241733 Tasks: T157889406, T154618477 --- .../renderer/components/view/macOS/KeyEvent.h | 49 +++++++++++++++++++ .../components/view/macOS/MacOSViewProps.cpp | 13 ++++- .../components/view/macOS/MacOSViewProps.h | 4 ++ .../components/view/macOS/conversions.h | 40 +++++++++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 ReactCommon/react/renderer/components/view/macOS/KeyEvent.h create mode 100644 ReactCommon/react/renderer/components/view/macOS/conversions.h diff --git a/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h b/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h new file mode 100644 index 00000000000000..fb3f2709a36908 --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { + +/* + * Describes a request to handle a key input. + */ +struct HandledKey { + /** + * The key for the event aligned to https://www.w3.org/TR/uievents-key/. + */ + std::string key{}; + + /* + * A flag indicating if the alt key is pressed. + */ + std::optional altKey{}; + + /* + * A flag indicating if the control key is pressed. + */ + std::optional ctrlKey{}; + + /* + * A flag indicating if the shift key is pressed. + */ + std::optional shiftKey{}; + + /* + * A flag indicating if the meta key is pressed. + */ + std::optional metaKey{}; +}; + +inline static bool operator==(const HandledKey &lhs, const HandledKey &rhs) { + return lhs.key == rhs.key && lhs.altKey == rhs.altKey && lhs.ctrlKey == rhs.ctrlKey && + lhs.shiftKey == rhs.shiftKey && lhs.metaKey == rhs.metaKey; +} + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp index b39cf09d57af93..2fbb16b6e7352d 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -7,6 +7,7 @@ #include "MacOSViewProps.h" +#include #include #include @@ -24,7 +25,15 @@ MacOSViewProps::MacOSViewProps( enableFocusRing( CoreFeatures::enablePropIteratorSetter ? sourceProps.enableFocusRing - : convertRawProp(context, rawProps, "enableFocusRing", sourceProps.enableFocusRing, true)); + : convertRawProp(context, rawProps, "enableFocusRing", sourceProps.enableFocusRing, true)), + validKeysDown( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.validKeysDown + : convertRawProp(context, rawProps, "validKeysDown", sourceProps.validKeysDown, {})), + validKeysUp( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.validKeysUp + : convertRawProp(context, rawProps, "validKeysUp", sourceProps.validKeysUp, {})){}; void MacOSViewProps::setProp( const PropsParserContext &context, @@ -34,6 +43,8 @@ void MacOSViewProps::setProp( switch (hash) { RAW_SET_PROP_SWITCH_CASE_BASIC(focusable, false); RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing, true); + RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysUp, {}); } } diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h index 45a2bf81d4c8fc..b4b652f00b7601 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -30,6 +31,9 @@ class MacOSViewProps { bool focusable{false}; bool enableFocusRing{true}; + + std::optional> validKeysDown{}; + std::optional> validKeysUp{}; }; } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/conversions.h b/ReactCommon/react/renderer/components/view/macOS/conversions.h new file mode 100644 index 00000000000000..d9bfd4abdbaa85 --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/conversions.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace facebook::react { + +inline void fromRawValue(const PropsParserContext &context, const RawValue &value, HandledKey &result) { + if (value.hasType>()) { + auto map = static_cast>(value); + for (const auto &pair : map) { + if (pair.first == "key") { + result.key = static_cast(pair.second); + } else if (pair.first == "altKey") { + result.altKey = static_cast(pair.second); + } else if (pair.first == "ctrlKey") { + result.ctrlKey = static_cast(pair.second); + } else if (pair.first == "shiftKey") { + result.shiftKey = static_cast(pair.second); + } else if (pair.first == "metaKey") { + result.metaKey = static_cast(pair.second); + } + } + } else if (value.hasType()) { + result.key = (std::string)value; + } +} + +} // namespace facebook::react From ed98b658bfbaeaabc3a2f9db598d37afa96487a7 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 30 Jan 2024 22:05:30 +0100 Subject: [PATCH 44/80] [fabric] Add key down/up event emitters to View Summary: This diff adds the event emitters that will be used by the keyboard handler of the View component. The `KeyEvent` struct provides the same fields that were previously submitted by Paper. An equals operator between `KeyEvent` and `HandledKey` allows to check if a key event matches the criteria defined by the handled key description. The event emitters were added using the HostPlatformEventEmitter so that the macOS specific changes would be kept separate from the iOS version. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53241730 Tasks: T157889406, T154618477 --- .../renderer/components/view/macOS/KeyEvent.h | 156 ++++++++++++------ .../view/macOS/MacOSViewEventEmitter.cpp | 42 +++++ .../view/macOS/MacOSViewEventEmitter.h | 25 +++ .../components/view/macOS/MacOSViewProps.cpp | 18 +- .../components/view/macOS/MacOSViewProps.h | 3 + .../components/view/macOS/conversions.h | 18 ++ .../components/view/macOS/primitives.h | 39 +++++ .../view/HostPlatformViewEventEmitter.h | 12 ++ 8 files changed, 263 insertions(+), 50 deletions(-) create mode 100644 ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp create mode 100644 ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h create mode 100644 ReactCommon/react/renderer/components/view/macOS/primitives.h diff --git a/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h b/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h index fb3f2709a36908..b0eeebed85d4d3 100644 --- a/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h +++ b/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h @@ -1,49 +1,107 @@ -/* - * 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. - */ - -#pragma once - -#include - -namespace facebook::react { - -/* - * Describes a request to handle a key input. - */ -struct HandledKey { - /** - * The key for the event aligned to https://www.w3.org/TR/uievents-key/. - */ - std::string key{}; - - /* - * A flag indicating if the alt key is pressed. - */ - std::optional altKey{}; - - /* - * A flag indicating if the control key is pressed. - */ - std::optional ctrlKey{}; - - /* - * A flag indicating if the shift key is pressed. - */ - std::optional shiftKey{}; - - /* - * A flag indicating if the meta key is pressed. - */ - std::optional metaKey{}; -}; - -inline static bool operator==(const HandledKey &lhs, const HandledKey &rhs) { - return lhs.key == rhs.key && lhs.altKey == rhs.altKey && lhs.ctrlKey == rhs.ctrlKey && - lhs.shiftKey == rhs.shiftKey && lhs.metaKey == rhs.metaKey; -} - -} // namespace facebook::react +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { + +/* + * Describes a request to handle a key input. + */ +struct HandledKey { + /** + * The key for the event aligned to https://www.w3.org/TR/uievents-key/. + */ + std::string key{}; + + /* + * A flag indicating if the alt key is pressed. + */ + std::optional altKey{}; + + /* + * A flag indicating if the control key is pressed. + */ + std::optional ctrlKey{}; + + /* + * A flag indicating if the shift key is pressed. + */ + std::optional shiftKey{}; + + /* + * A flag indicating if the meta key is pressed. + */ + std::optional metaKey{}; +}; + +inline static bool operator==(const HandledKey &lhs, const HandledKey &rhs) { + return lhs.key == rhs.key && lhs.altKey == rhs.altKey && lhs.ctrlKey == rhs.ctrlKey && + lhs.shiftKey == rhs.shiftKey && lhs.metaKey == rhs.metaKey; +} + +/** + * Key event emitted by handled key events. + */ +struct KeyEvent { + /** + * The key for the event aligned to https://www.w3.org/TR/uievents-key/. + */ + std::string key{}; + + /* + * A flag indicating if the alt key is pressed. + */ + bool altKey{false}; + + /* + * A flag indicating if the control key is pressed. + */ + bool ctrlKey{false}; + + /* + * A flag indicating if the shift key is pressed. + */ + bool shiftKey{false}; + + /* + * A flag indicating if the meta key is pressed. + */ + bool metaKey{false}; + + /* + * A flag indicating if the caps lock key is pressed. + */ + bool capsLockKey{false}; + + /* + * A flag indicating if the key on the numeric pad is pressed. + */ + bool numericPadKey{false}; + + /* + * A flag indicating if the help key is pressed. + */ + bool helpKey{false}; + + /* + * A flag indicating if a function key is pressed. + */ + bool functionKey{false}; +}; + +inline static bool operator==(const KeyEvent &lhs, const HandledKey &rhs) { + return lhs.key == rhs.key && + (!rhs.altKey.has_value() || lhs.altKey == *rhs.altKey) && + (!rhs.ctrlKey.has_value() || lhs.ctrlKey == *rhs.ctrlKey) && + (!rhs.shiftKey.has_value() || lhs.shiftKey == *rhs.shiftKey) && + (!rhs.metaKey.has_value() || lhs.metaKey == *rhs.metaKey); +} + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp new file mode 100644 index 00000000000000..6a6ab410c96a41 --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#include "MacOSViewEventEmitter.h" + +namespace facebook::react { + +#pragma mark - Keyboard Events + +static jsi::Value keyEventPayload(jsi::Runtime &runtime, KeyEvent const &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 MacOSViewEventEmitter::onKeyDown(KeyEvent const &keyEvent) const { + dispatchEvent( + "keyDown", + [keyEvent](jsi::Runtime &runtime) { return keyEventPayload(runtime, keyEvent); }, + EventPriority::AsynchronousBatched); +} + +void MacOSViewEventEmitter::onKeyUp(KeyEvent const &keyEvent) const { + dispatchEvent( + "keyUp", + [keyEvent](jsi::Runtime &runtime) { return keyEventPayload(runtime, keyEvent); }, + EventPriority::AsynchronousBatched); +} + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h new file mode 100644 index 00000000000000..b9d4553306d944 --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace facebook::react { + +class MacOSViewEventEmitter : public TouchEventEmitter { + public: + using TouchEventEmitter::TouchEventEmitter; + +#pragma mark - Keyboard Events + + void onKeyDown(KeyEvent const &keyEvent) const; + void onKeyUp(KeyEvent const &keyEvent) const; +}; + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp index 2fbb16b6e7352d..7956f9ba98dc6c 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -18,7 +18,10 @@ MacOSViewProps::MacOSViewProps( const MacOSViewProps &sourceProps, const RawProps &rawProps, bool shouldSetRawProps) - : focusable( + : macOSViewEvents( + CoreFeatures::enablePropIteratorSetter ? sourceProps.macOSViewEvents + : convertRawProp(context, rawProps, sourceProps.macOSViewEvents, {})), + focusable( CoreFeatures::enablePropIteratorSetter ? sourceProps.focusable : convertRawProp(context, rawProps, "focusable", sourceProps.focusable, {})), @@ -35,12 +38,25 @@ MacOSViewProps::MacOSViewProps( ? sourceProps.validKeysUp : convertRawProp(context, rawProps, "validKeysUp", sourceProps.validKeysUp, {})){}; +#define VIEW_EVENT_CASE_MACOS(eventType, eventString) \ + case CONSTEXPR_RAW_PROPS_KEY_HASH(eventString): { \ + MacOSViewEvents defaultViewEvents{}; \ + bool res = defaultViewEvents[eventType]; \ + if (value.hasValue()) { \ + fromRawValue(context, value, res); \ + } \ + macOSViewEvents[eventType] = res; \ + return; \ + } + void MacOSViewProps::setProp( const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, RawValue const &value) { switch (hash) { + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyDown, "onKeyDown"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyUp, "onKeyUp"); RAW_SET_PROP_SWITCH_CASE_BASIC(focusable, false); RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing, true); RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown, {}); diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h index b4b652f00b7601..cff1a6a706fe2d 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -29,6 +30,8 @@ class MacOSViewProps { const char *propName, RawValue const &value); + MacOSViewEvents macOSViewEvents{}; + bool focusable{false}; bool enableFocusRing{true}; diff --git a/ReactCommon/react/renderer/components/view/macOS/conversions.h b/ReactCommon/react/renderer/components/view/macOS/conversions.h index d9bfd4abdbaa85..f45caa5fb60b9d 100644 --- a/ReactCommon/react/renderer/components/view/macOS/conversions.h +++ b/ReactCommon/react/renderer/components/view/macOS/conversions.h @@ -9,13 +9,31 @@ #include #include +#include #include #include #include +#include namespace facebook::react { +static inline MacOSViewEvents convertRawProp( + const PropsParserContext &context, + const RawProps &rawProps, + const MacOSViewEvents &sourceValue, + const MacOSViewEvents &defaultValue) { + MacOSViewEvents result{}; + using Offset = MacOSViewEvents::Offset; + + result[Offset::KeyDown] = + convertRawProp(context, rawProps, "onKeyDown", sourceValue[Offset::KeyDown], defaultValue[Offset::KeyDown]); + result[Offset::KeyUp] = + convertRawProp(context, rawProps, "onKeyUp", sourceValue[Offset::KeyUp], defaultValue[Offset::KeyUp]); + + return result; +} + inline void fromRawValue(const PropsParserContext &context, const RawValue &value, HandledKey &result) { if (value.hasType>()) { auto map = static_cast>(value); diff --git a/ReactCommon/react/renderer/components/view/macOS/primitives.h b/ReactCommon/react/renderer/components/view/macOS/primitives.h new file mode 100644 index 00000000000000..d9414f8ba80db2 --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/primitives.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { + +struct MacOSViewEvents { + std::bitset<8> bits{}; + + enum class Offset : uint8_t { + KeyDown = 1, + KeyUp = 2, + }; + + constexpr bool operator[](const Offset offset) const { + return bits[static_cast(offset)]; + } + + std::bitset<8>::reference operator[](const Offset offset) { + return bits[static_cast(offset)]; + } +}; + +inline static bool operator==(MacOSViewEvents const &lhs, MacOSViewEvents const &rhs) { + return lhs.bits == rhs.bits; +} + +inline static bool operator!=(MacOSViewEvents const &lhs, MacOSViewEvents const &rhs) { + return lhs.bits != rhs.bits; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h index 6f0924885fda6d..ba323a7928efdd 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h @@ -7,8 +7,20 @@ #pragma once +#if TARGET_OS_OSX + +#include + +namespace facebook::react { +using HostPlatformViewEventEmitter = MacOSViewEventEmitter; +} // namespace facebook::react + +#else + #include namespace facebook::react { using HostPlatformViewEventEmitter = TouchEventEmitter; } // namespace facebook::react + +#endif From 070fd4ee73ce05b2a71ebf2f3c177b6df55cbad4 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 30 Jan 2024 22:10:11 +0100 Subject: [PATCH 45/80] [fabric] Add key down/up handling to View component Summary: This diff adds key down/up handling to the View component. The key events are checked against the provided list of key strokes that should be manually handled. If a match is found, the corresponding keyDown/Up event emitter is provided with the captured key even. This will allow the View component to receive keyboard events and respond accordingly. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53241731 Tasks: T157889406, T154618477 --- .../View/RCTViewComponentView.mm | 69 +++++++++++++++++++ 1 file changed, 69 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 a8903d2125d5cc..54ba864cd6b270 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -18,6 +18,9 @@ #import #import // [macOS] #import // [macOS] +#if TARGET_OS_OSX // [macOS +#import // [macOS] +#endif // macOS] #import #import #import @@ -1032,6 +1035,72 @@ - (BOOL)didActivateAccessibilityCustomAction:(UIAccessibilityCustomAction *)acti } } +#if TARGET_OS_OSX // [macOS + +#pragma mark - Keyboard Events + +- (BOOL)handleKeyboardEvent:(NSEvent *)event { + BOOL keyDown = event.type == NSEventTypeKeyDown; + BOOL hasHandler = keyDown ? _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::KeyDown] + : _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::KeyUp]; + if (hasHandler) { + auto validKeys = keyDown ? _props->validKeysDown : _props->validKeysUp; + + // If the view is focusable and the component didn't explicity set the validKeysDown or validKeysUp, + // allow enter/return and spacebar key events to mimic the behavior of native controls. + if (self.focusable && !validKeys.has_value()) { + validKeys = { { .key = "Enter" }, { .key = " " } }; + } + + // Convert the event to a KeyEvent + NSEventModifierFlags modifierFlags = event.modifierFlags; + facebook::react::KeyEvent keyEvent = { + .key = [[RCTViewKeyboardEvent keyFromEvent:event] UTF8String], + .altKey = static_cast(modifierFlags & NSEventModifierFlagOption), + .ctrlKey = static_cast(modifierFlags & NSEventModifierFlagControl), + .shiftKey = static_cast(modifierFlags & NSEventModifierFlagShift), + .metaKey = static_cast(modifierFlags & NSEventModifierFlagCommand), + .capsLockKey = static_cast(modifierFlags & NSEventModifierFlagCapsLock), + .numericPadKey = static_cast(modifierFlags & NSEventModifierFlagNumericPad), + .helpKey = static_cast(modifierFlags & NSEventModifierFlagHelp), + .functionKey = static_cast(modifierFlags & NSEventModifierFlagFunction), + }; + + BOOL shouldBlock = NO; + for (auto const &validKey : *validKeys) { + if (keyEvent == validKey) { + shouldBlock = YES; + break; + } + } + + if (shouldBlock) { + if (keyDown) { + _eventEmitter->onKeyDown(keyEvent); + } else { + _eventEmitter->onKeyUp(keyEvent); + } + return YES; + } + } + + return NO; +} + +- (void)keyDown:(NSEvent *)event { + if (![self handleKeyboardEvent:event]) { + [super keyDown:event]; + } +} + +- (void)keyUp:(NSEvent *)event { + if (![self handleKeyboardEvent:event]) { + [super keyUp:event]; + } +} + +#endif // macOS] + - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point { return _eventEmitter; From 60d4b5db71c5bbb138a0fda23b2b191d5b343462 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 30 Jan 2024 18:44:10 +0100 Subject: [PATCH 46/80] [fabric] Add keyboard event handling to TextInput component Summary: This diff adds keyboard event handling to the TextInput component by testing the valid keys for manual keyboard event handling and dispatching key events to the View component keyboard handling method. Test Plan: - Run Workplace Chat with Fabric enabled. - Open a message thread with 3 or more participants. - Type `@` to display the reference selector. - Change the selection with the up and down arrows and select an entry with tab to test the custom keyboard event handling. https://pxl.cl/4ghtH Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53241729 Tasks: T157889406, T154618477 --- .../TextInput/RCTTextInputComponentView.mm | 22 +++++++++++++++++-- .../View/RCTViewComponentView.h | 4 ++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 2d2e4bc45d9d09..47b1c7ab80b357 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -506,7 +506,25 @@ - (void)grammarCheckingDidChange:(BOOL)enabled } - (BOOL)hasValidKeyDownOrValidKeyUp:(nonnull NSString *)key { - return YES; + std::string keyString = key.UTF8String; + + if (_props->validKeysDown.has_value()) { + for (auto const &validKey : *_props->validKeysDown) { + if (validKey.key == keyString) { + return YES; + } + } + } + + if (_props->validKeysUp.has_value()) { + for (auto const &validKey : *_props->validKeysUp) { + if (validKey.key == keyString) { + return YES; + } + } + } + + return NO; } - (void)submitOnKeyDownIfNeeded:(nonnull NSEvent *)event @@ -593,7 +611,7 @@ - (BOOL)textInputShouldHandleDragOperation:(nonnull id)draggingI } - (BOOL)textInputShouldHandleKeyEvent:(nonnull NSEvent *)event { - return YES; + return ![self handleKeyboardEvent:event]; } - (BOOL)textInputShouldHandlePaste:(nonnull id)sender { diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h index 3d7564de46e8b1..82f1536af92159 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h @@ -75,6 +75,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask NS_REQUIRES_SUPER; - (void)prepareForRecycle NS_REQUIRES_SUPER; +#if TARGET_OS_OSX // [macOS +- (BOOL)handleKeyboardEvent:(NSEvent *)event; +#endif // macOS] + /* * This is a fragment of temporary workaround that we need only temporary and will get rid of soon. */ From 94acea9d131744e3c002e3c9988253af2b5b721c Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 31 Jan 2024 00:44:32 +0100 Subject: [PATCH 47/80] [fabric] Fix missing includes for windows build Summary: The keyboard handling changes were not including `optional` which was an issue for the windows build of RN. Test Plan: Sandcastle build: D53244901 Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Subscribers: ericroz, chpurrer Differential Revision: https://phabricator.intern.facebook.com/D53248606 --- ReactCommon/react/renderer/components/view/macOS/KeyEvent.h | 1 + .../react/renderer/components/view/macOS/MacOSViewProps.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h b/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h index b0eeebed85d4d3..cb3496218c0ab8 100644 --- a/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h +++ b/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h @@ -8,6 +8,7 @@ #pragma once #include +#include namespace facebook::react { diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h index cff1a6a706fe2d..15359fd477f817 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -12,6 +12,8 @@ #include #include +#include + namespace facebook::react { class MacOSViewProps { From 29de2653767cb16ff9f010280ed2c63e41de30f0 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 7 Feb 2024 16:42:57 +0100 Subject: [PATCH 48/80] [fabric] Add mouse event props to View Summary: This diff adds the `onMouseEnter` and `onMouseLeave` event handler props and event emitters to the View component in Fabric. The mouse event data is the same that was provided by the Paper implementation in `RCTView`. These changes were made in the macOS specific extension of the View component props. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53529016 Tasks: T154617556 --- .../view/macOS/MacOSViewEventEmitter.cpp | 27 +++++++++ .../view/macOS/MacOSViewEventEmitter.h | 6 ++ .../components/view/macOS/MacOSViewProps.cpp | 2 + .../components/view/macOS/MouseEvent.h | 59 +++++++++++++++++++ .../components/view/macOS/conversions.h | 5 ++ .../components/view/macOS/primitives.h | 3 + 6 files changed, 102 insertions(+) create mode 100644 ReactCommon/react/renderer/components/view/macOS/MouseEvent.h diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp index 6a6ab410c96a41..274cf0d8e7941d 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp @@ -39,4 +39,31 @@ void MacOSViewEventEmitter::onKeyUp(KeyEvent const &keyEvent) const { EventPriority::AsynchronousBatched); } +static jsi::Value mouseEventPayload(jsi::Runtime &runtime, MouseEvent const &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 MacOSViewEventEmitter::onMouseEnter(MouseEvent const &mouseEvent) const { + dispatchEvent( + "mouseEnter", + [mouseEvent](jsi::Runtime &runtime) { return mouseEventPayload(runtime, mouseEvent); }, + EventPriority::AsynchronousBatched); +} + +void MacOSViewEventEmitter::onMouseLeave(MouseEvent const &mouseEvent) const { + dispatchEvent( + "mouseLeave", + [mouseEvent](jsi::Runtime &runtime) { return mouseEventPayload(runtime, mouseEvent); }, + EventPriority::AsynchronousBatched); +} + } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h index b9d4553306d944..58b71428e44dfc 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h @@ -9,6 +9,7 @@ #include #include +#include namespace facebook::react { @@ -20,6 +21,11 @@ class MacOSViewEventEmitter : public TouchEventEmitter { void onKeyDown(KeyEvent const &keyEvent) const; void onKeyUp(KeyEvent const &keyEvent) const; + +#pragma mark - Mouse Events + + void onMouseEnter(MouseEvent const &mouseEvent) const; + void onMouseLeave(MouseEvent const &mouseEvent) const; }; } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp index 7956f9ba98dc6c..288a1315ccb19b 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -57,6 +57,8 @@ void MacOSViewProps::setProp( switch (hash) { VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyDown, "onKeyDown"); VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyUp, "onKeyUp"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseEnter, "onMouseEnter"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseLeave, "onMouseLeave"); RAW_SET_PROP_SWITCH_CASE_BASIC(focusable, false); RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing, true); RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown, {}); diff --git a/ReactCommon/react/renderer/components/view/macOS/MouseEvent.h b/ReactCommon/react/renderer/components/view/macOS/MouseEvent.h new file mode 100644 index 00000000000000..457083eec2fc4e --- /dev/null +++ b/ReactCommon/react/renderer/components/view/macOS/MouseEvent.h @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { + +/* + * Describes a mouse enter/leave event. + */ +struct MouseEvent { + /** + * Pointer horizontal location in target view. + */ + Float clientX{0}; + + /** + * Pointer vertical location in target view. + */ + Float clientY{0}; + + /** + * Pointer horizontal location in window. + */ + Float screenX{0}; + + /** + * Pointer vertical location in window. + */ + Float screenY{0}; + + /* + * A flag indicating if the alt key is pressed. + */ + bool altKey{false}; + + /* + * A flag indicating if the control key is pressed. + */ + bool ctrlKey{false}; + + /* + * A flag indicating if the shift key is pressed. + */ + bool shiftKey{false}; + + /* + * A flag indicating if the meta key is pressed. + */ + bool metaKey{false}; +}; + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/conversions.h b/ReactCommon/react/renderer/components/view/macOS/conversions.h index f45caa5fb60b9d..79f9e108b39f20 100644 --- a/ReactCommon/react/renderer/components/view/macOS/conversions.h +++ b/ReactCommon/react/renderer/components/view/macOS/conversions.h @@ -31,6 +31,11 @@ static inline MacOSViewEvents convertRawProp( result[Offset::KeyUp] = convertRawProp(context, rawProps, "onKeyUp", sourceValue[Offset::KeyUp], defaultValue[Offset::KeyUp]); + result[Offset::MouseEnter] = + convertRawProp(context, rawProps, "onMouseEnter", sourceValue[Offset::MouseEnter], defaultValue[Offset::MouseEnter]); + result[Offset::MouseLeave] = + convertRawProp(context, rawProps, "onMouseLeave", sourceValue[Offset::MouseLeave], defaultValue[Offset::MouseLeave]); + return result; } diff --git a/ReactCommon/react/renderer/components/view/macOS/primitives.h b/ReactCommon/react/renderer/components/view/macOS/primitives.h index d9414f8ba80db2..855ffd6abd5206 100644 --- a/ReactCommon/react/renderer/components/view/macOS/primitives.h +++ b/ReactCommon/react/renderer/components/view/macOS/primitives.h @@ -17,6 +17,9 @@ struct MacOSViewEvents { enum class Offset : uint8_t { KeyDown = 1, KeyUp = 2, + + MouseEnter = 3, + MouseLeave = 4, }; constexpr bool operator[](const Offset offset) const { From c17a1dc7227b46258f3d9848bd18822c36114775 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 7 Feb 2024 16:49:26 +0100 Subject: [PATCH 49/80] [fabric] Add mouse enter/leave tracking to the View component Summary: This diff implements mouse enter/leave tracking for the area covered by the View component. The tracking is handled by configuring a tracking area on the NSView when either of the handlers is set on the view. This enables the NSResponder `mouseEntered:` and `mouseExited:` notifications which are translated to mouse events. Test Plan: * Run Zeratul with Fabric enabled. * Move the cursor over controls that have hover-dependent styling. https://pxl.cl/4jSw5 Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53529015 Tasks: T154617556 --- .../View/RCTViewComponentView.mm | 106 ++++++++++++++++++ 1 file changed, 106 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 54ba864cd6b270..d2385c1e06b9bf 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -38,6 +38,8 @@ @implementation RCTViewComponentView { BOOL _needsInvalidateLayer; BOOL _isJSResponder; BOOL _removeClippedSubviews; + BOOL _hasMouseOver; // [macOS] + NSTrackingArea *_trackingArea; // [macOS] NSMutableArray *_reactSubviews; // [macOS] NSSet *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN; } @@ -499,6 +501,8 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask _needsInvalidateLayer = NO; [self invalidateLayer]; + + [self updateTrackingAreas]; } - (void)prepareForRecycle @@ -1099,6 +1103,108 @@ - (void)keyUp:(NSEvent *)event { } } + +#pragma mark - Mouse Events + +- (void)sendMouseEvent:(BOOL)isMouseOver { + NSPoint locationInWindow = self.window.mouseLocationOutsideOfEventStream; + NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil]; + + NSEventModifierFlags modifierFlags = self.window.currentEvent.modifierFlags; + + MouseEvent mouseEvent = { + .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), + }; + + if (isMouseOver) { + _eventEmitter->onMouseEnter(mouseEvent); + } else { + _eventEmitter->onMouseLeave(mouseEvent); + } +} + +- (void)updateMouseOverIfNeeded +{ + // When an enclosing scrollview is scrolled using the scrollWheel or trackpad, + // the mouseExited: event does not get called on the view where mouseEntered: was previously called. + // This creates an unnatural pairing of mouse enter and exit events and can cause problems. + // We therefore explicitly check for this here and handle them by calling the appropriate callbacks. + + BOOL hasMouseOver = _hasMouseOver; + NSPoint locationInWindow = self.window.mouseLocationOutsideOfEventStream; + NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil]; + BOOL insideBounds = NSPointInRect(locationInView, self.visibleRect); + + // On macOS 14.0 visibleRect can be larger than the view bounds + insideBounds &= NSPointInRect(locationInView, self.bounds); + + if (hasMouseOver && !insideBounds) { + hasMouseOver = NO; + } else if (!hasMouseOver && insideBounds) { + // The window's frame view must be used for hit testing against `locationInWindow` + NSView *hitView = [self.window.contentView.superview hitTest:locationInWindow]; + hasMouseOver = [hitView isDescendantOf:self]; + } + + if (hasMouseOver != _hasMouseOver) { + _hasMouseOver = hasMouseOver; + [self sendMouseEvent:hasMouseOver]; + } +} + +- (void)updateTrackingAreas +{ + if (_trackingArea) { + [self removeTrackingArea:_trackingArea]; + } + + if ( + _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseEnter] || + _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseLeave] + ) { + _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds + options:NSTrackingActiveAlways | NSTrackingMouseEnteredAndExited + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; + [self updateMouseOverIfNeeded]; + } + + [super updateTrackingAreas]; +} + +- (void)mouseEntered:(NSEvent *)event +{ + if (_hasMouseOver) { + return; + } + + // The window's frame view must be used for hit testing against `locationInWindow` + NSView *hitView = [self.window.contentView.superview hitTest:event.locationInWindow]; + if (![hitView isDescendantOf:self]) { + return; + } + + _hasMouseOver = YES; + [self sendMouseEvent:_hasMouseOver]; +} + +- (void)mouseExited:(NSEvent *)event +{ + if (!_hasMouseOver) { + return; + } + + _hasMouseOver = NO; + [self sendMouseEvent:_hasMouseOver]; +} #endif // macOS] - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point From 21860423e2b6829f3048f8f9c54357fd93f52d8e Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 7 Feb 2024 16:50:57 +0100 Subject: [PATCH 50/80] [fabric] Disable view flattening for views using mouse events Summary: Views having mouse event handlers assigned to them should not be flattened so that the mouse tracking for the area they cover would work at all times. This diff disabled view flattening if either of the mouse event handlers is set. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53529017 Tasks: T154617556 --- .../react/renderer/components/view/ViewShadowNode.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp index 76ff482ad16fcf..911e74f81e0546 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/ViewShadowNode.cpp @@ -56,6 +56,8 @@ void ViewShadowNode::initialize() noexcept { #if TARGET_OS_OSX // [macOS || viewProps.focusable || viewProps.enableFocusRing + || viewProps.macOSViewEvents[MacOSViewEvents::Offset::MouseEnter] + || viewProps.macOSViewEvents[MacOSViewEvents::Offset::MouseLeave] #endif // macOS] ; From 403abec75fb6685ec6f0093392737643e590aa29 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 7 Feb 2024 18:35:56 +0100 Subject: [PATCH 51/80] [fabric] Add mouse enter/leave tracking on scroll Summary: This diff adds a bounds change observer to the wrapping clip view, allowing to be notified when the view position is changing because a parent scroll view is being scrolled. This allows the view to evaluate the new cursor position and check if mouse enter/leave events have to be emitted. Test Plan: * Run Zeratul with Fabric enabled. * Scroll the messages view without moving the cursor position and check that enter/leave tracking is working as expected. https://pxl.cl/4jSxh Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53529018 Tasks: T154617556 --- .../View/RCTViewComponentView.mm | 34 +++++++++++++++++++ 1 file changed, 34 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 d2385c1e06b9bf..6f8930b2ed9d2b 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -39,6 +39,7 @@ @implementation RCTViewComponentView { BOOL _isJSResponder; BOOL _removeClippedSubviews; BOOL _hasMouseOver; // [macOS] + BOOL _hasClipViewBoundsObserver; // [macOS] NSTrackingArea *_trackingArea; // [macOS] NSMutableArray *_reactSubviews; // [macOS] NSSet *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN; @@ -503,6 +504,7 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask [self invalidateLayer]; [self updateTrackingAreas]; + [self updateClipViewBoundsObserverIfNeeded]; } - (void)prepareForRecycle @@ -1159,6 +1161,38 @@ - (void)updateMouseOverIfNeeded } } +- (void)updateClipViewBoundsObserverIfNeeded +{ + // Subscribe to view bounds changed notification so that the view can be notified when a + // scroll event occurs either due to trackpad/gesture based scrolling or a scrollwheel event + // both of which would not cause the mouseExited to be invoked. + + NSClipView *clipView = self.window ? self.enclosingScrollView.contentView : nil; + + BOOL hasMouseEventHandler = _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseEnter] || + _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseLeave]; + + if (_hasClipViewBoundsObserver && (!clipView || !hasMouseEventHandler)) { + _hasClipViewBoundsObserver = NO; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSViewBoundsDidChangeNotification + object:nil]; + } else if (!_hasClipViewBoundsObserver && clipView && hasMouseEventHandler) { + _hasClipViewBoundsObserver = YES; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateMouseOverIfNeeded) + name:NSViewBoundsDidChangeNotification + object:clipView]; + [self updateMouseOverIfNeeded]; + } +} + +- (void)viewDidMoveToWindow +{ + [self updateClipViewBoundsObserverIfNeeded]; + [super viewDidMoveToWindow]; +} + - (void)updateTrackingAreas { if (_trackingArea) { From 00e38a223b548488db7b085b8e6e3a0e3ad241bf Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 8 Feb 2024 03:11:34 +0100 Subject: [PATCH 52/80] [fabric] Check for assigned event emitter before sending key/mouse events Summary: This diff adds null checks to avoid sending key and/or mouse events before the event emitter was assigned to the component. Test Plan: * Run Zeratul with Fabric enabled * Hover over buttons * Use up/down arrows in composer on user tag selector https://pxl.cl/4jVCb Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53555261 --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 6 +++++- 1 file changed, 5 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 6f8930b2ed9d2b..8b7c3b048935ba 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1080,7 +1080,7 @@ - (BOOL)handleKeyboardEvent:(NSEvent *)event { } } - if (shouldBlock) { + if (_eventEmitter && shouldBlock) { if (keyDown) { _eventEmitter->onKeyDown(keyEvent); } else { @@ -1109,6 +1109,10 @@ - (void)keyUp:(NSEvent *)event { #pragma mark - Mouse Events - (void)sendMouseEvent:(BOOL)isMouseOver { + if (!_eventEmitter) { + return; + } + NSPoint locationInWindow = self.window.mouseLocationOutsideOfEventStream; NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil]; From 160454fe66280038d48f6c3f079da062a693f9b5 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 12 Feb 2024 19:59:39 +0100 Subject: [PATCH 53/80] [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 --- .../components/view/macOS/MacOSViewProps.cpp | 7 +++++- .../components/view/macOS/MacOSViewProps.h | 2 ++ .../View/RCTViewComponentView.mm | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp index 288a1315ccb19b..fcf4d1e14cff7f 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -36,7 +36,11 @@ MacOSViewProps::MacOSViewProps( validKeysUp( CoreFeatures::enablePropIteratorSetter ? sourceProps.validKeysUp - : convertRawProp(context, rawProps, "validKeysUp", sourceProps.validKeysUp, {})){}; + : convertRawProp(context, rawProps, "validKeysUp", sourceProps.validKeysUp, {})), + draggedTypes( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.draggedTypes + : convertRawProp(context, rawProps, "draggedTypes", sourceProps.draggedTypes, {})){}; #define VIEW_EVENT_CASE_MACOS(eventType, eventString) \ case CONSTEXPR_RAW_PROPS_KEY_HASH(eventString): { \ @@ -63,6 +67,7 @@ void MacOSViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing, true); RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown, {}); RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysUp, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(draggedTypes, {}); } } diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h index 15359fd477f817..be496081b7533b 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -39,6 +39,8 @@ class MacOSViewProps { std::optional> validKeysDown{}; std::optional> validKeysUp{}; + + std::optional> draggedTypes{}; }; } // namespace facebook::react 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 8b7c3b048935ba..b804bfe687f1d8 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -446,6 +446,28 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.focusable = (bool)newViewProps.focusable; // `enableFocusRing` self.enableFocusRing = (bool)newViewProps.enableFocusRing; + + // `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; From b5b4ba9b5fe653a4e2d5e41ce4e75b50d65d8c93 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 12 Feb 2024 20:06:06 +0100 Subject: [PATCH 54/80] [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/macOS/MacOSViewEventEmitter.cpp | 63 ++++++++++++++++++- .../view/macOS/MacOSViewEventEmitter.h | 6 ++ .../components/view/macOS/MouseEvent.h | 14 +++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp index 274cf0d8e7941d..3ac3187ce63dd2 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp @@ -39,7 +39,7 @@ void MacOSViewEventEmitter::onKeyUp(KeyEvent const &keyEvent) const { EventPriority::AsynchronousBatched); } -static jsi::Value mouseEventPayload(jsi::Runtime &runtime, MouseEvent const &event) { +static jsi::Object mouseEventPayload(jsi::Runtime &runtime, MouseEvent const &event) { auto payload = jsi::Object(runtime); payload.setProperty(runtime, "clientX", event.clientX); payload.setProperty(runtime, "clientY", event.clientY); @@ -66,4 +66,65 @@ void MacOSViewEventEmitter::onMouseLeave(MouseEvent const &mouseEvent) const { EventPriority::AsynchronousBatched); } +static jsi::Value dragEventPayload(jsi::Runtime &runtime, DragEvent const &event) { + auto filesArray = jsi::Array(runtime, event.dataTransferItems.size()); + auto itemsArray = jsi::Array(runtime, event.dataTransferItems.size()); + auto typesArray = jsi::Array(runtime, event.dataTransferItems.size()); + int i = 0; + for (auto const &transferItem : event.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); + + auto payload = mouseEventPayload(runtime, event); + payload.setProperty(runtime, "dataTransfer", dataTransferObject); + return payload; +}; + +void MacOSViewEventEmitter::onDragEnter(DragEvent const &dragEvent) const { + dispatchEvent( + "dragEnter", + [dragEvent](jsi::Runtime &runtime) { return dragEventPayload(runtime, dragEvent); }, + EventPriority::AsynchronousBatched); +} + +void MacOSViewEventEmitter::onDragLeave(DragEvent const &dragEvent) const { + dispatchEvent( + "dragLeave", + [dragEvent](jsi::Runtime &runtime) { return dragEventPayload(runtime, dragEvent); }, + EventPriority::AsynchronousBatched); +} + +void MacOSViewEventEmitter::onDrop(DragEvent const &dragEvent) const { + dispatchEvent( + "drop", + [dragEvent](jsi::Runtime &runtime) { return dragEventPayload(runtime, dragEvent); }, + EventPriority::AsynchronousBatched); +} + } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h index 58b71428e44dfc..3ade79cf238946 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h @@ -26,6 +26,12 @@ class MacOSViewEventEmitter : public TouchEventEmitter { 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 diff --git a/ReactCommon/react/renderer/components/view/macOS/MouseEvent.h b/ReactCommon/react/renderer/components/view/macOS/MouseEvent.h index 457083eec2fc4e..aafa96e7917ec7 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MouseEvent.h +++ b/ReactCommon/react/renderer/components/view/macOS/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 b121fc33700f41f3d8d1de7154a970436b8e91cb Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 12 Feb 2024 20:12:18 +0100 Subject: [PATCH 55/80] [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 b804bfe687f1d8..d0e404166f793b 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1128,6 +1128,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)sendMouseEvent:(BOOL)isMouseOver { From 2c06459f53dd3760c08df6f15fee283d3101a5a2 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Mon, 12 Feb 2024 20:14:28 +0100 Subject: [PATCH 56/80] [fabric] Add drag and drop support to TextInput component Summary: Add support for drag and drop to the TextInput component. This re-uses the drag and drop handlers implemented on the View component to support dropping images/files on TextInput views directly. Test Plan: * Run Zeratul with Fabric enabled * Drag and drop an image on the message composer * Send the attachment https://pxl.cl/4ldBX Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53674740 --- .../TextInput/RCTTextInputComponentView.mm | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 47b1c7ab80b357..ef68cde4b7774b 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -591,22 +591,32 @@ - (void)textInputDidCancel } - (NSDragOperation)textInputDraggingEntered:(nonnull id)draggingInfo { + if ([draggingInfo.draggingPasteboard availableTypeFromArray:self.registeredDraggedTypes]) { + return [self draggingEntered:draggingInfo]; + } return NSDragOperationNone; } - (void)textInputDraggingExited:(nonnull id)draggingInfo { - return; + if ([draggingInfo.draggingPasteboard availableTypeFromArray:self.registeredDraggedTypes]) { + [self draggingExited:draggingInfo]; + } } -- (BOOL)textInputShouldHandleDeleteBackward:(nonnull id)sender { +- (BOOL)textInputShouldHandleDragOperation:(nonnull id)draggingInfo { + if ([draggingInfo.draggingPasteboard availableTypeFromArray:self.registeredDraggedTypes]) { + [self performDragOperation:draggingInfo]; + return NO; + } + return YES; } -- (BOOL)textInputShouldHandleDeleteForward:(nonnull id)sender { +- (BOOL)textInputShouldHandleDeleteBackward:(nonnull id)sender { return YES; } -- (BOOL)textInputShouldHandleDragOperation:(nonnull id)draggingInfo { +- (BOOL)textInputShouldHandleDeleteForward:(nonnull id)sender { return YES; } From 9be4bfb777b4a810dc2d8e24cd997fb4934544b9 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 13 Feb 2024 02:26:41 +0100 Subject: [PATCH 57/80] [fabric] Limit draggedTypes prop values to supported options Summary: Update draggedType prop to use enums and limit input to supported options. Test Plan: * Run Zeratul with Fabric enabled * Drag an image over the messages view and send the image https://pxl.cl/4ljBt Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53694154 Tasks: T178960804 --- .../renderer/components/view/macOS/MacOSViewProps.h | 2 +- .../renderer/components/view/macOS/conversions.h | 13 +++++++++++++ .../renderer/components/view/macOS/primitives.h | 6 ++++++ .../ComponentViews/View/RCTViewComponentView.mm | 6 +++--- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h index be496081b7533b..ecd3a1d5e6a111 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -40,7 +40,7 @@ class MacOSViewProps { std::optional> validKeysDown{}; std::optional> validKeysUp{}; - std::optional> draggedTypes{}; + std::optional> draggedTypes{}; }; } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/conversions.h b/ReactCommon/react/renderer/components/view/macOS/conversions.h index 79f9e108b39f20..632d90f919897a 100644 --- a/ReactCommon/react/renderer/components/view/macOS/conversions.h +++ b/ReactCommon/react/renderer/components/view/macOS/conversions.h @@ -60,4 +60,17 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu } } +inline void fromRawValue(const PropsParserContext &context, const RawValue &value, DraggedType &result) { + auto string = (std::string)value; + if (string == "fileUrl") { + result = DraggedType::FileUrl; + } else if (string == "image") { + result = DraggedType::Image; + } else if (string == "string") { + result = DraggedType::String; + } else { + abort(); + } +} + } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/primitives.h b/ReactCommon/react/renderer/components/view/macOS/primitives.h index 855ffd6abd5206..37377ccfa3af7b 100644 --- a/ReactCommon/react/renderer/components/view/macOS/primitives.h +++ b/ReactCommon/react/renderer/components/view/macOS/primitives.h @@ -11,6 +11,12 @@ namespace facebook::react { +enum class DraggedType { + FileUrl, + Image, + String, +}; + struct MacOSViewEvents { std::bitset<8> bits{}; 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 d0e404166f793b..7f7d6ac0a9591d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -456,12 +456,12 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (newViewProps.draggedTypes.has_value()) { NSMutableArray *pasteboardTypes = [NSMutableArray new]; for (const auto &draggedType : *newViewProps.draggedTypes) { - if (draggedType == "fileUrl") { + if (draggedType == DraggedType::FileUrl) { [pasteboardTypes addObject:NSFilenamesPboardType]; - } else if (draggedType == "image") { + } else if (draggedType == DraggedType::Image) { [pasteboardTypes addObject:NSPasteboardTypePNG]; [pasteboardTypes addObject:NSPasteboardTypeTIFF]; - } else if (draggedType == "string") { + } else if (draggedType == DraggedType::String) { [pasteboardTypes addObject:NSPasteboardTypeString]; } } From 53d01152c24b202acba33304f0d40ff6cd20d870 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 13 Feb 2024 04:40:22 +0100 Subject: [PATCH 58/80] [fabric] Add file paste event emitter to TextInput component Summary: This diff refactors the data transfer item payload conversion logic to share it with `TextInputEventEmitter` paste event, allowing to implement the onPaste event emitters building the same payload as generated by the drag and drop implementation. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53694921 --- .../view/macOS/MacOSViewEventEmitter.cpp | 17 +++++++++++------ .../view/macOS/MacOSViewEventEmitter.h | 2 ++ .../iostextinput/TextInputEventEmitter.cpp | 9 +++++++++ .../iostextinput/TextInputEventEmitter.h | 8 ++++++++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp index 3ac3187ce63dd2..7e12b2cf259085 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp @@ -66,12 +66,12 @@ void MacOSViewEventEmitter::onMouseLeave(MouseEvent const &mouseEvent) const { EventPriority::AsynchronousBatched); } -static jsi::Value dragEventPayload(jsi::Runtime &runtime, DragEvent const &event) { - auto filesArray = jsi::Array(runtime, event.dataTransferItems.size()); - auto itemsArray = jsi::Array(runtime, event.dataTransferItems.size()); - auto typesArray = jsi::Array(runtime, event.dataTransferItems.size()); +jsi::Value MacOSViewEventEmitter::dataTransferPayload(jsi::Runtime &runtime, std::vector const &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 (auto const &transferItem : event.dataTransferItems) { + for (auto const &transferItem : dataTransferItems) { auto fileObject = jsi::Object(runtime); fileObject.setProperty(runtime, "name", transferItem.name); fileObject.setProperty(runtime, "type", transferItem.type); @@ -100,11 +100,16 @@ static jsi::Value dragEventPayload(jsi::Runtime &runtime, DragEvent const &event dataTransferObject.setProperty(runtime, "files", filesArray); dataTransferObject.setProperty(runtime, "items", itemsArray); dataTransferObject.setProperty(runtime, "types", typesArray); + + return dataTransferObject; +} +static jsi::Value dragEventPayload(jsi::Runtime &runtime, DragEvent const &event) { auto payload = mouseEventPayload(runtime, event); + auto dataTransferObject = MacOSViewEventEmitter::dataTransferPayload(runtime, event.dataTransferItems); payload.setProperty(runtime, "dataTransfer", dataTransferObject); return payload; -}; +} void MacOSViewEventEmitter::onDragEnter(DragEvent const &dragEvent) const { dispatchEvent( diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h index 3ade79cf238946..afef5717502e02 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h @@ -32,6 +32,8 @@ class MacOSViewEventEmitter : public TouchEventEmitter { void onDragEnter(DragEvent const &dragEvent) const; void onDragLeave(DragEvent const &dragEvent) const; void onDrop(DragEvent const &dragEvent) const; + + static jsi::Value dataTransferPayload(jsi::Runtime &runtime, std::vector const &dataTransferItems); }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp index d5185ae72f659e..447fe8d760fd07 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.cpp @@ -193,6 +193,15 @@ void TextInputEventEmitter::onGrammarCheckChange(OnGrammarCheckChange event) con return payload; }); } + +void TextInputEventEmitter::onPaste(PasteEvent const &pasteEvent) const { + dispatchEvent("paste", [pasteEvent](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + auto dataTransfer= dataTransferPayload(runtime, pasteEvent.dataTransferItems); + payload.setProperty(runtime, "dataTransfer", dataTransfer); + return payload; + }); +} #endif // macOS] } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h index 06e16d9d94cde1..2ca5038beeaf7e 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h @@ -11,6 +11,9 @@ #include namespace facebook::react { +#if TARGET_OS_OSX // [macOS +#include +#endif // macOS] class TextInputMetrics { public: @@ -63,6 +66,11 @@ class TextInputEventEmitter : public ViewEventEmitter { bool enabled; }; void onGrammarCheckChange(OnGrammarCheckChange value) const; + + struct PasteEvent { + std::vector dataTransferItems; + }; + void onPaste(PasteEvent const &pasteEvent) const; #endif // macOS] private: From 81a1e3db7f010b4c67ac9e810dc8206b3b8c876a Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 13 Feb 2024 04:43:10 +0100 Subject: [PATCH 59/80] [fabric] Add file paste support to TextInput component Summary: Implement the file/image pasteboard pasting event for the `TextInput` component in Fabric. The payload for the event is the same as for the drag and drop events. Test Plan: * Run Zeratul with Fabric enabled * Copy an image/file on the pasteboard * Focus the message composer * Paste the image/file and send the message https://pxl.cl/4lk92 Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53694922 --- .../TextInput/RCTTextInputComponentView.mm | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index ef68cde4b7774b..be611cd5c9bc2a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -625,7 +625,24 @@ - (BOOL)textInputShouldHandleKeyEvent:(nonnull NSEvent *)event { } - (BOOL)textInputShouldHandlePaste:(nonnull id)sender { - return YES; + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSPasteboardType fileType = [pasteboard availableTypeFromArray:@[NSFilenamesPboardType, NSPasteboardTypePNG, NSPasteboardTypeTIFF]]; + NSArray* pastedTypes = ((RCTUITextView*) _backedTextInputView).readablePasteboardTypes; + + // If there's a fileType that is of interest, notify JS. Also blocks notifying JS if it's a text paste + if (_eventEmitter && fileType != nil && [pastedTypes containsObject:fileType]) { + auto const &textInputEventEmitter = *std::static_pointer_cast(_eventEmitter); + std::vector dataTransferItems{}; + [self buildDataTransferItems:dataTransferItems forPasteboard:pasteboard]; + + TextInputEventEmitter::PasteEvent pasteEvent = { + .dataTransferItems = dataTransferItems, + }; + textInputEventEmitter.onPaste(pasteEvent); + } + + // Only allow pasting text. + return fileType == nil; } #endif // macOS] From 7ecb2f6fe1b2fd8c80a9019b4b4c2c7004e9df0a Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 13 Feb 2024 15:39:36 +0100 Subject: [PATCH 60/80] [fabric] Fix missing method declaration of View component Summary: See title. Test Plan: Sandcastle build D53695263 Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53711568 --- .../Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h index 82f1536af92159..acb7b62db877c3 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h @@ -77,6 +77,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_OSX // [macOS - (BOOL)handleKeyboardEvent:(NSEvent *)event; +- (void)buildDataTransferItems:(std::vector &)dataTransferItems forPasteboard:(NSPasteboard *)pasteboard; #endif // macOS] /* From 1b5364e81bbc02e86474a0fd8b5f61a5831cf0f8 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 13 Feb 2024 18:50:59 +0100 Subject: [PATCH 61/80] [fabric] Fix namespace in View component header Summary: See title. Test Plan: Sandcastle build D53695263 Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53715088 --- .../Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h index acb7b62db877c3..6479fccd17f476 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h @@ -77,7 +77,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_OSX // [macOS - (BOOL)handleKeyboardEvent:(NSEvent *)event; -- (void)buildDataTransferItems:(std::vector &)dataTransferItems forPasteboard:(NSPasteboard *)pasteboard; +- (void)buildDataTransferItems:(std::vector &)dataTransferItems forPasteboard:(NSPasteboard *)pasteboard; #endif // macOS] /* From 6f05e54d90fd317bf1bc814b5437bac283cc0399 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 20 Feb 2024 03:22:57 +0100 Subject: [PATCH 62/80] [fabric] Fix crash on key up in non focusable view Summary: This diff fixes a crash happening when a key up/down event was processed by a non-focusable view with no "handled keys" configured on the view. This would lead to an iteration over an optional vector that had no values set. Test Plan: * Run Zeratul with Fabric enabled * Focus the search field * Tab to toggle the focus until it reaches the message thread list * Tab to the next focusable view | Before | After | |--| | https://pxl.cl/4n91l | https://pxl.cl/4n910 | | Crash when tabbing out of thread list | No more crash | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53930899 --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 5 +++++ 1 file changed, 5 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 7f7d6ac0a9591d..dc59db1b070be0 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1079,6 +1079,11 @@ - (BOOL)handleKeyboardEvent:(NSEvent *)event { if (self.focusable && !validKeys.has_value()) { validKeys = { { .key = "Enter" }, { .key = " " } }; } + + // If there are no valid keys defined, no key event handling is required. + if (!validKeys.has_value()) { + return NO; + } // Convert the event to a KeyEvent NSEventModifierFlags modifierFlags = event.modifierFlags; From 8b630fb93a44b7c8f9e54517c1b74d00b3d73a0e Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 20 Feb 2024 03:50:27 +0100 Subject: [PATCH 63/80] [fabric] Add missing pragma marks in macOS view event emitter Summary: See title. Test Plan: Sandcastle build. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53931156 --- .../components/view/macOS/MacOSViewEventEmitter.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp index 7e12b2cf259085..d15500bd4a4057 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp @@ -39,6 +39,9 @@ void MacOSViewEventEmitter::onKeyUp(KeyEvent const &keyEvent) const { EventPriority::AsynchronousBatched); } + +#pragma mark - Mouse Events + static jsi::Object mouseEventPayload(jsi::Runtime &runtime, MouseEvent const &event) { auto payload = jsi::Object(runtime); payload.setProperty(runtime, "clientX", event.clientX); @@ -66,6 +69,9 @@ void MacOSViewEventEmitter::onMouseLeave(MouseEvent const &mouseEvent) const { EventPriority::AsynchronousBatched); } + +#pragma mark - Drag and Drop Events + jsi::Value MacOSViewEventEmitter::dataTransferPayload(jsi::Runtime &runtime, std::vector const &dataTransferItems) { auto filesArray = jsi::Array(runtime, dataTransferItems.size()); auto itemsArray = jsi::Array(runtime, dataTransferItems.size()); From 8c6444ae9eeabf28d7603db8c19da80816bf951d Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 20 Feb 2024 03:51:04 +0100 Subject: [PATCH 64/80] [fabric] Add focus/blur event emitters to View Summary: This diff extends the macOS view event emitter to support sending blur and focus events. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53931155 --- .../components/view/macOS/MacOSViewEventEmitter.cpp | 11 +++++++++++ .../components/view/macOS/MacOSViewEventEmitter.h | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp index d15500bd4a4057..73deb163445dbb 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp @@ -138,4 +138,15 @@ void MacOSViewEventEmitter::onDrop(DragEvent const &dragEvent) const { EventPriority::AsynchronousBatched); } + +#pragma mark - Focus Events + +void MacOSViewEventEmitter::onFocus() const { + dispatchEvent("focus"); +} + +void MacOSViewEventEmitter::onBlur() const { + dispatchEvent("blur"); +} + } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h index afef5717502e02..0bdba8652765a5 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h @@ -33,6 +33,11 @@ class MacOSViewEventEmitter : public TouchEventEmitter { void onDragLeave(DragEvent const &dragEvent) const; void onDrop(DragEvent const &dragEvent) const; +#pragma mark - Focus Events + + void onFocus() const; + void onBlur() const; + static jsi::Value dataTransferPayload(jsi::Runtime &runtime, std::vector const &dataTransferItems); }; From 699610c79daca4eee2064bdb66f3ac818a0e34d5 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 20 Feb 2024 03:52:57 +0100 Subject: [PATCH 65/80] [fabric] Add View focus/blur event emitting on first responder change Summary: This diff overrides the first responder tracking methods to submit focus/blur events when the native view becomes the first responder (focus) and resigns the first responder state (blur). Test Plan: * Update a view to log to the console when it is focused/blurred * Run Zeratul with Fabric enabled * Focus/blur the view and check the logs to verify that the event handlers are being called https://pxl.cl/4n99N Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D53931157 --- .../View/RCTViewComponentView.mm | 29 +++++++++++++++++++ 1 file changed, 29 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 dc59db1b070be0..1bd2690408e7bd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1065,6 +1065,35 @@ - (BOOL)didActivateAccessibilityCustomAction:(UIAccessibilityCustomAction *)acti #if TARGET_OS_OSX // [macOS +#pragma mark - Focus Events + +- (BOOL)becomeFirstResponder +{ + if (![super becomeFirstResponder]) { + return NO; + } + + if (_eventEmitter) { + _eventEmitter->onFocus(); + } + + return YES; +} + +- (BOOL)resignFirstResponder +{ + if (![super resignFirstResponder]) { + return NO; + } + + if (_eventEmitter) { + _eventEmitter->onBlur(); + } + + return YES; +} + + #pragma mark - Keyboard Events - (BOOL)handleKeyboardEvent:(NSEvent *)event { From 547d68316e0be0927111ad9b0670381a2868ef20 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 21 Feb 2024 17:18:53 +0100 Subject: [PATCH 66/80] [fabric] Add double click event prop to View component Summary: This diff adds the `doubleClick` event emitter to the macOS view event emitter with tracking of handler assignment on the component to test if a handler is set or not on the component to conditionally enable double click event forwarding. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D54007807 --- .../components/view/macOS/MacOSViewEventEmitter.cpp | 7 +++++++ .../renderer/components/view/macOS/MacOSViewEventEmitter.h | 1 + .../renderer/components/view/macOS/MacOSViewProps.cpp | 1 + .../react/renderer/components/view/macOS/conversions.h | 3 +++ .../react/renderer/components/view/macOS/primitives.h | 3 +++ 5 files changed, 15 insertions(+) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp index 73deb163445dbb..032b3350aabdd0 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp @@ -69,6 +69,13 @@ void MacOSViewEventEmitter::onMouseLeave(MouseEvent const &mouseEvent) const { EventPriority::AsynchronousBatched); } +void MacOSViewEventEmitter::onDoubleClick(MouseEvent const &mouseEvent) const { + dispatchEvent( + "doubleClick", + [mouseEvent](jsi::Runtime &runtime) { return mouseEventPayload(runtime, mouseEvent); }, + EventPriority::AsynchronousBatched); +} + #pragma mark - Drag and Drop Events diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h index 0bdba8652765a5..bda6a1bb82f639 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h @@ -26,6 +26,7 @@ class MacOSViewEventEmitter : public TouchEventEmitter { void onMouseEnter(MouseEvent const &mouseEvent) const; void onMouseLeave(MouseEvent const &mouseEvent) const; + void onDoubleClick(MouseEvent const &mouseEvent) const; #pragma mark - Drag and Drop Events diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp index fcf4d1e14cff7f..828cf3b8c19e51 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -63,6 +63,7 @@ void MacOSViewProps::setProp( VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyUp, "onKeyUp"); VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseEnter, "onMouseEnter"); VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseLeave, "onMouseLeave"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::DoubleClick, "onDoubleClick"); RAW_SET_PROP_SWITCH_CASE_BASIC(focusable, false); RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing, true); RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown, {}); diff --git a/ReactCommon/react/renderer/components/view/macOS/conversions.h b/ReactCommon/react/renderer/components/view/macOS/conversions.h index 632d90f919897a..ec5c7748aa8ed8 100644 --- a/ReactCommon/react/renderer/components/view/macOS/conversions.h +++ b/ReactCommon/react/renderer/components/view/macOS/conversions.h @@ -36,6 +36,9 @@ static inline MacOSViewEvents convertRawProp( result[Offset::MouseLeave] = convertRawProp(context, rawProps, "onMouseLeave", sourceValue[Offset::MouseLeave], defaultValue[Offset::MouseLeave]); + result[Offset::DoubleClick] = + convertRawProp(context, rawProps, "onDoubleClick", sourceValue[Offset::DoubleClick], defaultValue[Offset::DoubleClick]); + return result; } diff --git a/ReactCommon/react/renderer/components/view/macOS/primitives.h b/ReactCommon/react/renderer/components/view/macOS/primitives.h index 37377ccfa3af7b..9a8a73154bbf3d 100644 --- a/ReactCommon/react/renderer/components/view/macOS/primitives.h +++ b/ReactCommon/react/renderer/components/view/macOS/primitives.h @@ -21,11 +21,14 @@ struct MacOSViewEvents { std::bitset<8> bits{}; enum class Offset : uint8_t { + // Keyboard Events KeyDown = 1, KeyUp = 2, + // Mouse Events MouseEnter = 3, MouseLeave = 4, + DoubleClick = 5, }; constexpr bool operator[](const Offset offset) const { From ba2378ac63de2e655122261417ed10b83c963a26 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Wed, 21 Feb 2024 17:24:54 +0100 Subject: [PATCH 67/80] [fabric] Add double click support to View component Summary: This diff adds support for double click event handling to the `View` component by overriding the `mouseUp` NSResponder method and track when a double click happened for a component that has JS double click handler set. The mouse event generation method was refactored to use an enum for the event type to support enter/leave/double-click event emissions. Test Plan: * Run Zeratul with Fabric enabled * Double click on the window drag handler area on the top of the window * The JS-based window zoom action should be triggered https://pxl.cl/4nwrl Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D54007810 --- .../View/RCTViewComponentView.mm | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 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 1bd2690408e7bd..178cac074df3a2 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1316,7 +1316,13 @@ - (BOOL)performDragOperation:(id )sender #pragma mark - Mouse Events -- (void)sendMouseEvent:(BOOL)isMouseOver { +enum MouseEventType { + MouseEnter, + MouseLeave, + DoubleClick, +}; + +- (void)sendMouseEvent:(MouseEventType)eventType { if (!_eventEmitter) { return; } @@ -1337,10 +1343,18 @@ - (void)sendMouseEvent:(BOOL)isMouseOver { .metaKey = static_cast(modifierFlags & NSEventModifierFlagCommand), }; - if (isMouseOver) { - _eventEmitter->onMouseEnter(mouseEvent); - } else { - _eventEmitter->onMouseLeave(mouseEvent); + switch (eventType) { + case MouseEnter: + _eventEmitter->onMouseEnter(mouseEvent); + break; + + case MouseLeave: + _eventEmitter->onMouseLeave(mouseEvent); + break; + + case DoubleClick: + _eventEmitter->onDoubleClick(mouseEvent); + break; } } @@ -1369,7 +1383,7 @@ - (void)updateMouseOverIfNeeded if (hasMouseOver != _hasMouseOver) { _hasMouseOver = hasMouseOver; - [self sendMouseEvent:hasMouseOver]; + [self sendMouseEvent:hasMouseOver ? MouseEnter : MouseLeave]; } } @@ -1426,6 +1440,16 @@ - (void)updateTrackingAreas [super updateTrackingAreas]; } +- (void)mouseUp:(NSEvent *)event +{ + BOOL hasDoubleClickEventHandler = _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::DoubleClick]; + if (hasDoubleClickEventHandler && event.clickCount == 2) { + [self sendMouseEvent:DoubleClick]; + } else { + [super mouseUp:event]; + } +} + - (void)mouseEntered:(NSEvent *)event { if (_hasMouseOver) { @@ -1439,7 +1463,7 @@ - (void)mouseEntered:(NSEvent *)event } _hasMouseOver = YES; - [self sendMouseEvent:_hasMouseOver]; + [self sendMouseEvent:MouseEnter]; } - (void)mouseExited:(NSEvent *)event @@ -1449,7 +1473,7 @@ - (void)mouseExited:(NSEvent *)event } _hasMouseOver = NO; - [self sendMouseEvent:_hasMouseOver]; + [self sendMouseEvent:MouseLeave]; } #endif // macOS] From bb12e7a852b7dc69dbaa0cf133ed383ef4342632 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Fri, 26 Apr 2024 17:54:13 -0700 Subject: [PATCH 68/80] [fabric] Make RCTViewComponentView to be layer-backed by enabling wantsUpdateLayer Summary: As title Test Plan: TBD I believe this is tested after cloning changes to fbsource and then testing there? Reviewers: shawndempsey, lefever, helenistic Reviewed By: lefever Differential Revision: https://phabricator.intern.facebook.com/D53783607 Tasks: T163838856 --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 5 +++++ 1 file changed, 5 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 178cac074df3a2..13d247ed2346bd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1487,6 +1487,11 @@ - (NSString *)componentViewName_DO_NOT_USE_THIS_IS_BROKEN return RCTNSStringFromString([[self class] componentDescriptorProvider].name); } +- (BOOL)wantsUpdateLayer +{ + return YES; +} + @end #ifdef __cplusplus From b0e32bb1b7714d7e48e4f29d533ac9b8ae63c754 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 22 Feb 2024 03:05:03 +0100 Subject: [PATCH 69/80] [fabric] Clean up explicit namespace references in View component Summary: See title. Test Plan: Tested later in this stack. Reviewers: shawndempsey, chpurrer, #rn-desktop Reviewed By: chpurrer Differential Revision: https://phabricator.intern.facebook.com/D54045718 --- .../ComponentViews/View/RCTViewComponentView.mm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 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 13d247ed2346bd..61e9fe34300a19 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1098,8 +1098,8 @@ - (BOOL)resignFirstResponder - (BOOL)handleKeyboardEvent:(NSEvent *)event { BOOL keyDown = event.type == NSEventTypeKeyDown; - BOOL hasHandler = keyDown ? _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::KeyDown] - : _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::KeyUp]; + BOOL hasHandler = keyDown ? _props->macOSViewEvents[MacOSViewEvents::Offset::KeyDown] + : _props->macOSViewEvents[MacOSViewEvents::Offset::KeyUp]; if (hasHandler) { auto validKeys = keyDown ? _props->validKeysDown : _props->validKeysUp; @@ -1395,8 +1395,9 @@ - (void)updateClipViewBoundsObserverIfNeeded NSClipView *clipView = self.window ? self.enclosingScrollView.contentView : nil; - BOOL hasMouseEventHandler = _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseEnter] || - _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseLeave]; + + BOOL hasMouseEventHandler = _props->macOSViewEvents[MacOSViewEvents::Offset::MouseEnter] || + _props->macOSViewEvents[MacOSViewEvents::Offset::MouseLeave]; if (_hasClipViewBoundsObserver && (!clipView || !hasMouseEventHandler)) { _hasClipViewBoundsObserver = NO; @@ -1426,8 +1427,8 @@ - (void)updateTrackingAreas } if ( - _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseEnter] || - _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::MouseLeave] + _props->macOSViewEvents[MacOSViewEvents::Offset::MouseEnter] || + _props->macOSViewEvents[MacOSViewEvents::Offset::MouseLeave] ) { _trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingActiveAlways | NSTrackingMouseEnteredAndExited @@ -1442,7 +1443,7 @@ - (void)updateTrackingAreas - (void)mouseUp:(NSEvent *)event { - BOOL hasDoubleClickEventHandler = _props->macOSViewEvents[facebook::react::MacOSViewEvents::Offset::DoubleClick]; + BOOL hasDoubleClickEventHandler = _props->macOSViewEvents[MacOSViewEvents::Offset::DoubleClick]; if (hasDoubleClickEventHandler && event.clickCount == 2) { [self sendMouseEvent:DoubleClick]; } else { From 62470f4486fe84464b5d4035217c231f1ec8acb5 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Thu, 22 Feb 2024 03:11:02 +0100 Subject: [PATCH 70/80] [fabric] Add support for setting a tool tip on View component Summary: This diff adds the macOS `tooltip` property to the View component and assigns the provided text to the native NSView `toolTip` property. Test Plan: * Run Zeratul with Fabric enabled * Hover over buttons and check that a tool tip is displayed. https://pxl.cl/4nM7h Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D54045717 --- .../renderer/components/view/macOS/MacOSViewProps.cpp | 7 ++++++- .../renderer/components/view/macOS/MacOSViewProps.h | 3 +++ .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 9 +++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp index 828cf3b8c19e51..40611de3299ea2 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -40,7 +40,11 @@ MacOSViewProps::MacOSViewProps( draggedTypes( CoreFeatures::enablePropIteratorSetter ? sourceProps.draggedTypes - : convertRawProp(context, rawProps, "draggedTypes", sourceProps.draggedTypes, {})){}; + : convertRawProp(context, rawProps, "draggedTypes", sourceProps.draggedTypes, {})), + tooltip( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.tooltip + : convertRawProp(context, rawProps, "tooltip", sourceProps.tooltip, {})){}; #define VIEW_EVENT_CASE_MACOS(eventType, eventString) \ case CONSTEXPR_RAW_PROPS_KEY_HASH(eventString): { \ @@ -69,6 +73,7 @@ void MacOSViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown, {}); RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysUp, {}); RAW_SET_PROP_SWITCH_CASE_BASIC(draggedTypes, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(tooltip, {}); } } diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h index ecd3a1d5e6a111..b2010849fa60f1 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -13,6 +13,7 @@ #include #include +#include namespace facebook::react { @@ -41,6 +42,8 @@ class MacOSViewProps { std::optional> validKeysUp{}; std::optional> draggedTypes{}; + + std::optional tooltip{}; }; } // namespace facebook::react 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 61e9fe34300a19..2797ed0c4f5aaf 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -468,6 +468,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & [self registerForDraggedTypes:pasteboardTypes]; } } + + // `tooltip` + if (oldViewProps.tooltip != newViewProps.tooltip) { + if (newViewProps.tooltip.has_value()) { + self.toolTip = RCTNSStringFromStringNilIfEmpty(newViewProps.tooltip.value()); + } else { + self.toolTip = nil; + } + } #endif // macOS] _needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer; From ed66784ba314714d2834f6fef3a6baa4f4804b0e Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 23 Feb 2024 02:49:41 +0100 Subject: [PATCH 71/80] [fabric] Add cursor prop to View component Summary: This diff adds the `cursor` prop to the View component in Fabric. The supported cursor types are converted to the `facebook::react::Cursor` type, supported the same set of values in Paper for the same prop. Test Plan: Tested later in this stack. Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D54098203 Tasks: T157889735 --- .../components/view/macOS/MacOSViewProps.cpp | 7 ++- .../components/view/macOS/MacOSViewProps.h | 1 + .../components/view/macOS/conversions.h | 47 +++++++++++++++++++ .../components/view/macOS/primitives.h | 22 +++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp index 40611de3299ea2..96534fe71c7826 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp @@ -44,7 +44,11 @@ MacOSViewProps::MacOSViewProps( tooltip( CoreFeatures::enablePropIteratorSetter ? sourceProps.tooltip - : convertRawProp(context, rawProps, "tooltip", sourceProps.tooltip, {})){}; + : convertRawProp(context, rawProps, "tooltip", sourceProps.tooltip, {})), + cursor( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.cursor + : convertRawProp(context, rawProps, "cursor", sourceProps.cursor, {})){}; #define VIEW_EVENT_CASE_MACOS(eventType, eventString) \ case CONSTEXPR_RAW_PROPS_KEY_HASH(eventString): { \ @@ -74,6 +78,7 @@ void MacOSViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysUp, {}); RAW_SET_PROP_SWITCH_CASE_BASIC(draggedTypes, {}); RAW_SET_PROP_SWITCH_CASE_BASIC(tooltip, {}); + RAW_SET_PROP_SWITCH_CASE_BASIC(cursor, {}); } } diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h index b2010849fa60f1..1e153a5a3b8b46 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h @@ -44,6 +44,7 @@ class MacOSViewProps { std::optional> draggedTypes{}; std::optional tooltip{}; + std::optional cursor{}; }; } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/conversions.h b/ReactCommon/react/renderer/components/view/macOS/conversions.h index ec5c7748aa8ed8..18c3bfde5b5c7c 100644 --- a/ReactCommon/react/renderer/components/view/macOS/conversions.h +++ b/ReactCommon/react/renderer/components/view/macOS/conversions.h @@ -76,4 +76,51 @@ inline void fromRawValue(const PropsParserContext &context, const RawValue &valu } } +inline void fromRawValue(const PropsParserContext &context, const RawValue &value, Cursor &result) { + auto string = (std::string)value; + if (string == "alias") { + result = Cursor::DragLink; + } else if (string == "auto") { + result = Cursor::Auto; + } else if (string == "col-resize") { + result = Cursor::ResizeLeftRight; + } else if (string == "context-menu") { + result = Cursor::ContextualMenu; + } else if (string == "copy") { + result = Cursor::DragCopy; + } else if (string == "crosshair") { + result = Cursor::Crosshair; + } else if (string == "default") { + result = Cursor::Arrow; + } else if (string == "disappearing-item") { + result = Cursor::DisappearingItem; + } else if (string == "e-resize") { + result = Cursor::ResizeRight; + } else if (string == "grab") { + result = Cursor::OpenHand; + } else if (string == "grabbing") { + result = Cursor::ClosedHand; + } else if (string == "n-resize") { + result = Cursor::ResizeUp; + } else if (string == "no-drop") { + result = Cursor::OperationNotAllowed; + } else if (string == "not-allowed") { + result = Cursor::OperationNotAllowed; + } else if (string == "pointer") { + result = Cursor::PointingHand; + } else if (string == "row-resize") { + result = Cursor::ResizeUpDown; + } else if (string == "s-resize") { + result = Cursor::ResizeDown; + } else if (string == "text") { + result = Cursor::IBeam; + } else if (string == "vertical-text") { + result = Cursor::IBeamCursorForVerticalLayout; + } else if (string == "w-resize") { + result = Cursor::ResizeLeft; + } else { + result = Cursor::Auto; + } +} + } // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/primitives.h b/ReactCommon/react/renderer/components/view/macOS/primitives.h index 9a8a73154bbf3d..4dae459b55f1d3 100644 --- a/ReactCommon/react/renderer/components/view/macOS/primitives.h +++ b/ReactCommon/react/renderer/components/view/macOS/primitives.h @@ -17,6 +17,28 @@ enum class DraggedType { String, }; +enum class Cursor { + Arrow, + Auto, + ClosedHand, + ContextualMenu, + Crosshair, + DisappearingItem, + DragCopy, + DragLink, + IBeam, + IBeamCursorForVerticalLayout, + OpenHand, + OperationNotAllowed, + PointingHand, + ResizeDown, + ResizeLeft, + ResizeLeftRight, + ResizeRight, + ResizeUp, + ResizeUpDown, +}; + struct MacOSViewEvents { std::bitset<8> bits{}; From 6b829271aa2348bd82308adf7113c7afab2429a6 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 23 Feb 2024 02:54:03 +0100 Subject: [PATCH 72/80] [fabric] Add support for updating cursor to View component Summary: This diff implements the conversion of the react cursor value to a native `NSCursor` instance which is used by the native view to update the current cursor for the surface covered by the view bounds. Test Plan: * Run Zeratul with Fabric enabled * Move the cursor to the vertical splitter * Check that the cursor changes depending on the possible resize direction. https://pxl.cl/4p28W Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D54098204 Tasks: T157889735 --- .../View/RCTViewComponentView.mm | 83 +++++++++++++++++++ 1 file changed, 83 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 2797ed0c4f5aaf..7750cbebaa402d 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,71 @@ using namespace facebook::react; +#if TARGET_OS_OSX // [macOS +inline NSCursor* RCTNSCursorFromCursor(facebook::react::Cursor cursorValue) { + NSCursor *cursor = nil; + + switch (cursorValue) { + case Cursor::Arrow: + cursor = [NSCursor arrowCursor]; + break; + case Cursor::ClosedHand: + cursor = [NSCursor closedHandCursor]; + break; + case Cursor::ContextualMenu: + cursor = [NSCursor contextualMenuCursor]; + break; + case Cursor::Crosshair: + cursor = [NSCursor crosshairCursor]; + break; + case Cursor::DisappearingItem: + cursor = [NSCursor disappearingItemCursor]; + break; + case Cursor::DragCopy: + cursor = [NSCursor dragCopyCursor]; + break; + case Cursor::DragLink: + cursor = [NSCursor dragLinkCursor]; + break; + case Cursor::IBeam: + cursor = [NSCursor IBeamCursor]; + break; + case Cursor::IBeamCursorForVerticalLayout: + cursor = [NSCursor IBeamCursorForVerticalLayout]; + break; + case Cursor::OpenHand: + cursor = [NSCursor openHandCursor]; + break; + case Cursor::OperationNotAllowed: + cursor = [NSCursor operationNotAllowedCursor]; + break; + case Cursor::PointingHand: + cursor = [NSCursor pointingHandCursor]; + break; + case Cursor::ResizeDown: + cursor = [NSCursor resizeDownCursor]; + break; + case Cursor::ResizeLeft: + cursor = [NSCursor resizeLeftCursor]; + break; + case Cursor::ResizeLeftRight: + cursor = [NSCursor resizeLeftRightCursor]; + break; + case Cursor::ResizeRight: + cursor = [NSCursor resizeRightCursor]; + break; + case Cursor::ResizeUp: + cursor = [NSCursor resizeUpCursor]; + break; + case Cursor::ResizeUpDown: + cursor = [NSCursor resizeUpDownCursor]; + break; + } + + return cursor; +} +#endif // macOS] + @implementation RCTViewComponentView { RCTUIColor *_backgroundColor; // [macOS] CALayer *_borderLayer; @@ -41,6 +106,7 @@ @implementation RCTViewComponentView { BOOL _hasMouseOver; // [macOS] BOOL _hasClipViewBoundsObserver; // [macOS] NSTrackingArea *_trackingArea; // [macOS] + NSCursor *_cursor; // [macOS] NSMutableArray *_reactSubviews; // [macOS] NSSet *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN; } @@ -477,6 +543,14 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & self.toolTip = nil; } } + + // `cursor` + if (oldViewProps.cursor != newViewProps.cursor) { + _cursor = nil; + if (newViewProps.cursor.has_value()) { + _cursor = RCTNSCursorFromCursor(newViewProps.cursor.value()); + } + } #endif // macOS] _needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer; @@ -1429,6 +1503,15 @@ - (void)viewDidMoveToWindow [super viewDidMoveToWindow]; } +- (void)resetCursorRects +{ + [self discardCursorRects]; + if (_cursor) { + [self addCursorRect:self.bounds cursor:_cursor]; + } + [self updateMouseOverIfNeeded]; +} + - (void)updateTrackingAreas { if (_trackingArea) { From 3a14d9b63b13c038a99d5ad68d202ebbb65e0569 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Wed, 13 Mar 2024 16:47:06 -0700 Subject: [PATCH 73/80] [fabric] TextInput should get focus with `autoFocus` prop Summary: **Context** - TextInput focus prop was not wired up for Fabric **Change** - Focus the text input if `autofocus={true}` Test Plan: Tested in top of stack Reviewers: lefever, #rn-desktop Reviewed By: lefever Differential Revision: https://phabricator.intern.facebook.com/D54880714 --- .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index be611cd5c9bc2a..fdf7b948a1a4e2 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -105,7 +105,12 @@ - (void)didMoveToWindow if (props.autoFocus) { #if !TARGET_OS_OSX // [macOS] [_backedTextInputView becomeFirstResponder]; -#endif // [macOS] +#else // [macOS + NSWindow *window = _backedTextInputView.window; + if (window) { + [window makeFirstResponder:_backedTextInputView.responder]; + } +#endif // macOS] } _didMoveToWindow = YES; } From 398ed3fa1679b74724f68ceb7c087ec85667e666 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 26 Mar 2024 15:15:43 +0100 Subject: [PATCH 74/80] [fabric] Fix random text display for undefined text content Summary: This diff resets the string content assigned to the internal text view on recycle for the Text component. Because the text view update exits early when no text storage is defined on the state of the component, the previous text would still be set on the text view and displayed when using a recycled component view with undefined content assigned to it. By resetting the text view to an empty string on recycle, that edge case will display an empty text component when being assigned undefined content. Test Plan: - Run Cosmo Studio and open the UI Reference - Update an example to include a Text component with undefined content - Check that no text is being rendered | Before | After | |--| | https://pxl.cl/4zTW2 | {F1473555501} | Reviewers: shawndempsey, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D55368305 Tasks: T170085811 --- .../ComponentViews/Text/RCTParagraphComponentView.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 1b5b20d1b06d5a..98bf93a96afb8c 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -233,6 +233,11 @@ - (void)prepareForRecycle [super prepareForRecycle]; _state.reset(); _accessibilityProvider = nil; + +#if TARGET_OS_OSX // [macOS + // Clear the text view to avoid displaying the previous text on recycle with undefined text content. + _textView.string = @""; +#endif // macOS] } - (void)drawRect:(CGRect)rect From 254f4db279abf096a053ab696f4c14b93f8bb5e1 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Wed, 24 Apr 2024 17:07:58 -0700 Subject: [PATCH 75/80] [fabric] TextInput should handle pasted types prop Summary: **Context** - Configuring paste options was never implemented in our Fabric TextInput - Paper Implementation: https://www.internalfb.com/code/fbsource/[93ff90a797da]/third-party/microsoft-fork-of-react-native/react-native-macos/packages/react-native/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m?lines=29-35 https://www.internalfb.com/code/fbsource/[563690cf01f1]/third-party/microsoft-fork-of-react-native/react-native-macos/packages/react-native/React/Base/RCTConvert.m?lines=1239 **Change** - Add c++ enum/prop - Add conversion - Add `pastedTypes` to Fabric component Test Plan: |Fabric|Paper| | https://pxl.cl/4LcPw | https://pxl.cl/4LcQL| Reviewers: lefever, #rn-desktop Differential Revision: https://phabricator.intern.facebook.com/D56558929 Tasks: T186086013 --- .../RCTBackedTextInputViewProtocol.h | 1 + .../TextInput/Singleline/RCTUITextField.h | 2 ++ .../TextInput/Singleline/RCTUITextField.mm | 8 +++++ .../TextInput/RCTTextInputComponentView.mm | 8 +++++ .../React/Fabric/RCTConversions.h | 29 +++++++++++++++++++ .../components/iostextinput/conversions.h | 22 ++++++++++++++ .../components/iostextinput/primitives.h | 15 +++++++++- .../iostextinput/propsConversions.h | 6 ++++ 8 files changed, 90 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h index ad80d3cc4b1d2d..97777009786f85 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h @@ -86,6 +86,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_OSX // [macOS // UITextInput method for OSX - (CGSize)sizeThatFits:(CGSize)size; +- (void)setReadablePasteBoardTypes:(NSArray *)readablePasteboardTypes; #endif // macOS] // This protocol disallows direct access to `text` property because diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h index ae55ec1c8b0683..ad699abed9f7bd 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h @@ -66,6 +66,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nullable) RCTUIColor *selectionColor; @property (weak, nullable) id delegate; @property (nonatomic, assign) CGFloat pointScaleFactor; + +- (void)setReadablePasteBoardTypes:(NSArray *)readablePasteboardTypes; #endif // macOS] @property (nonatomic, getter=isGhostTextChanging) BOOL ghostTextChanging; // [macOS] diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm index 45555d743298ef..dd2eb55ab5f123 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm @@ -93,6 +93,7 @@ @implementation RCTUITextField { #endif RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter; NSDictionary *_defaultTextAttributes; + NSArray *_readablePasteboardTypes; } #if TARGET_OS_OSX // [macOS @@ -665,5 +666,12 @@ - (void)keyUp:(NSEvent *)event { } } #endif // macOS] + +#if TARGET_OS_OSX // [macOS +- (void)setReadablePasteBoardTypes:(NSArray *)readablePasteboardTypes +{ + _readablePasteboardTypes = readablePasteboardTypes; +} +#endif // macOS] @end diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index fdf7b948a1a4e2..c88fcbf299b33b 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -272,6 +272,14 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & if (newTextInputProps.inputAccessoryViewID != oldTextInputProps.inputAccessoryViewID) { _backedTextInputView.inputAccessoryViewID = RCTNSStringFromString(newTextInputProps.inputAccessoryViewID); } + +#if TARGET_OS_OSX // [macOS + if (newTextInputProps.traits.pastedTypes!= oldTextInputProps.traits.pastedTypes) { + NSArray *types = RCTPasteboardTypeArrayFromProps(newTextInputProps.traits.pastedTypes); + [_backedTextInputView setReadablePasteBoardTypes:types]; + } +#endif // macOS] + [super updateProps:props oldProps:oldProps]; [self setDefaultInputAccessoryView]; diff --git a/packages/react-native/React/Fabric/RCTConversions.h b/packages/react-native/React/Fabric/RCTConversions.h index 09b27df970eddf..e7468de4578bd8 100644 --- a/packages/react-native/React/Fabric/RCTConversions.h +++ b/packages/react-native/React/Fabric/RCTConversions.h @@ -13,6 +13,10 @@ #import #import +#if TARGET_OS_OSX // [macOS +#import +#endif // macOS] + NS_ASSUME_NONNULL_BEGIN inline NSString *RCTNSStringFromString( @@ -294,4 +298,29 @@ inline facebook::react::LayoutDirection RCTLayoutDirection(BOOL isRTL) return isRTL ? facebook::react::LayoutDirection::RightToLeft : facebook::react::LayoutDirection::LeftToRight; } +#if TARGET_OS_OSX // [macOS +inline NSArray *RCTPasteboardTypeArrayFromProps(const std::vector &pastedTypes) +{ + NSMutableArray *types = [NSMutableArray new]; + + for (const auto &type : pastedTypes) { + switch (type) { + case facebook::react::PastedTypesType::FileUrl: + [types addObjectsFromArray:@[NSFilenamesPboardType]]; + break; + case facebook::react::PastedTypesType::Image: + [types addObjectsFromArray:@[NSPasteboardTypePNG, NSPasteboardTypeTIFF]]; + break; + case facebook::react::PastedTypesType::String: + [types addObjectsFromArray:@[NSPasteboardTypeString]]; + break; + default: + break; + } + } + + return [types copy]; +} +#endif // macOS] + NS_ASSUME_NONNULL_END diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/conversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/conversions.h index 93e30078534b30..cd9a37606b7888 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/conversions.h @@ -237,4 +237,26 @@ inline void fromRawValue( abort(); } +#ifdef TARGET_OS_OSX // [macOS +inline void fromRawValue( + const PropsParserContext &context, + const RawValue &value, + PastedTypesType &result) { + auto string = (std::string)value; + if (string == "fileUrl") { + result = PastedTypesType::FileUrl; + return; + } + if (string == "image") { + result = PastedTypesType::Image; + return; + } + if (string == "string") { + result = PastedTypesType::String; + return; + } + abort(); +} +#endif // macOS] + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h index de4e6f3d00648d..ad86ce6496cf53 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h @@ -99,6 +99,12 @@ class SubmitKeyEvent final { bool metaKey{false}; bool functionKey{false}; }; + +enum class PastedTypesType { + FileUrl, + Image, + String, +}; #endif // macOS] /* @@ -264,7 +270,14 @@ class TextInputTraits final { * macOS-only * Default value: `false` */ - bool clearTextOnSubmit{false}; + bool clearTextOnSubmit{false}; + + /* + * List of pastable types + * macOS-only + * Default value: `empty list` + */ + std::vector pastedTypes{}; #endif // macOS] }; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h index d593177f5cb848..1aa0757db94317 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h @@ -155,6 +155,12 @@ static TextInputTraits convertRawProp( "grammarCheck", sourceTraits.grammarCheck, defaultTraits.grammarCheck); + traits.pastedTypes = convertRawProp( + context, + rawProps, + "pastedTypes", + sourceTraits.pastedTypes, + defaultTraits.pastedTypes); #endif // macOS] return traits; From d6b404c5e4592c04e9c6a89c6381f14822d02b56 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Fri, 23 Feb 2024 00:20:42 +0100 Subject: [PATCH 76/80] [upstream][paper] Fix crash when statically enabling wantsUpdateLayer for View component Summary: This diff fixes an invariant that wasn't valid anymore after having enabled `wantsUpdateLayer` statically for the View component in Fabric. `RCTUIView` in RCTUIKit enables `wantsUpdateLayer` only if the instance implements the `displayLayer:` method. Because the View component always wants to have `wantsUpdateLayer` enabled, the assumption that `displayLayer:` exists wasn't valid anymore. This diff only calls the `displayLayer:` method if the instance effectively responds to it. To avoid a perf hit on the check, we only test for it in the initialization and cache the result. Test Plan: * Run the Cosmo app ``` ~/fbsource/xplat/arfx/cosmo/mac/run.sh ``` | Before | After | |--| | {F1460101180} | {F1460101226} | Reviewers: shawndempsey, jorgecab, #rn-desktop Reviewed By: shawndempsey Differential Revision: https://phabricator.intern.facebook.com/D54090975 # Conflicts: # packages/react-native/React/Base/macOS/RCTUIKit.m --- packages/react-native/React/Base/macOS/RCTUIKit.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Base/macOS/RCTUIKit.m b/packages/react-native/React/Base/macOS/RCTUIKit.m index 01c96db75eeeca..ae08d1d9e29172 100644 --- a/packages/react-native/React/Base/macOS/RCTUIKit.m +++ b/packages/react-native/React/Base/macOS/RCTUIKit.m @@ -216,6 +216,7 @@ @implementation RCTUIView BOOL _clipsToBounds; BOOL _userInteractionEnabled; BOOL _mouseDownCanMoveWindow; + BOOL _respondsToDisplayLayer; } + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key @@ -245,6 +246,7 @@ @implementation RCTUIView self->_userInteractionEnabled = YES; self->_enableFocusRing = YES; self->_mouseDownCanMoveWindow = YES; + self->_respondsToDisplayLayer = [self respondsToSelector:@selector(displayLayer:)]; } return self; } @@ -343,7 +345,12 @@ - (void)updateLayer // so it has to be reset from the view's NSColor ivar. [layer setBackgroundColor:[_backgroundColor CGColor]]; } - [(id)self displayLayer:layer]; + + // In Fabric, wantsUpdateLayer is always enabled and doesn't guarantee that + // the instance has a displayLayer method. + if (_respondsToDisplayLayer) { + [(id)self displayLayer:layer]; + } } - (void)drawRect:(CGRect)rect From a430e284a628a60f2f3453c3b4592ae9a469b2de Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Tue, 30 Apr 2024 23:21:41 -0700 Subject: [PATCH 77/80] [fabric][touch-handler] Remove `cancelTouchWithEvent` till surface touch handler is merged --- .../Mounting/ComponentViews/Text/RCTParagraphComponentView.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 98bf93a96afb8c..7f19346cf04b8d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -430,7 +430,6 @@ - (void)mouseDown:(NSEvent *)event // Start selection if we're still selectable and hit-testable. if (_textView.selectable && [contentView hitTest:point] == self) { - [[RCTSurfaceTouchHandler surfaceTouchHandlerForView:self] cancelTouchWithEvent:event]; [self.window makeFirstResponder:_textView]; [_textView mouseDown:event]; } From cc052f55bb97325ecb574f308947cd4a9d381370 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Tue, 30 Apr 2024 16:27:47 -0700 Subject: [PATCH 78/80] [fabric] Refactor MacOS view props & emitters --- .../components/view/macOS/MacOSViewProps.cpp | 85 ------------ .../components/view/macOS/MacOSViewProps.h | 50 ------- .../components/view/macOS/conversions.h | 126 ----------------- .../components/view/macOS/primitives.h | 73 ---------- .../ReactCommon/React-Fabric.podspec | 2 +- .../iostextinput/TextInputEventEmitter.h | 2 +- .../components/iostextinput/primitives.h | 1 + .../iostextinput/propsConversions.h | 13 +- .../components/view/BaseViewEventEmitter.h | 7 +- .../components/view/BaseViewProps.cpp | 3 - .../renderer/components/view/BaseViewProps.h | 4 +- .../view/HostPlatformViewEventEmitter.h | 26 ---- .../components/view/HostPlatformViewProps.h | 38 ------ .../renderer/components/view/DraggedType.h | 18 +++ .../components/view/HostPlatformTouch.h | 14 ++ .../view/HostPlatformViewEventEmitter.cpp | 37 +++-- .../view/HostPlatformViewEventEmitter.h | 90 ++++++------ .../components/view/HostPlatformViewProps.cpp | 129 ++++++++++++++++++ .../components/view/HostPlatformViewProps.h | 41 ++++++ .../view/HostPlatformViewTraitsInitializer.h | 27 ++++ .../renderer/components/view}/KeyEvent.h | 0 .../components/view/MacOSViewEvents.h | 73 ++++++++++ .../renderer/components/view}/MouseEvent.h | 0 23 files changed, 378 insertions(+), 481 deletions(-) delete mode 100644 ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp delete mode 100644 ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h delete mode 100644 ReactCommon/react/renderer/components/view/macOS/conversions.h delete mode 100644 ReactCommon/react/renderer/components/view/macOS/primitives.h delete mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h delete mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/DraggedType.h create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformTouch.h rename ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp => packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp (80%) rename ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h => packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h (75%) create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.h create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewTraitsInitializer.h rename {ReactCommon/react/renderer/components/view/macOS => packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view}/KeyEvent.h (100%) create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MacOSViewEvents.h rename {ReactCommon/react/renderer/components/view/macOS => packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view}/MouseEvent.h (100%) diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp deleted file mode 100644 index 96534fe71c7826..00000000000000 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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. - */ - -#include "MacOSViewProps.h" - -#include -#include -#include - -namespace facebook::react { - -MacOSViewProps::MacOSViewProps( - const PropsParserContext &context, - const MacOSViewProps &sourceProps, - const RawProps &rawProps, - bool shouldSetRawProps) - : macOSViewEvents( - CoreFeatures::enablePropIteratorSetter ? sourceProps.macOSViewEvents - : convertRawProp(context, rawProps, sourceProps.macOSViewEvents, {})), - focusable( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.focusable - : convertRawProp(context, rawProps, "focusable", sourceProps.focusable, {})), - enableFocusRing( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.enableFocusRing - : convertRawProp(context, rawProps, "enableFocusRing", sourceProps.enableFocusRing, true)), - validKeysDown( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.validKeysDown - : convertRawProp(context, rawProps, "validKeysDown", sourceProps.validKeysDown, {})), - validKeysUp( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.validKeysUp - : convertRawProp(context, rawProps, "validKeysUp", sourceProps.validKeysUp, {})), - draggedTypes( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.draggedTypes - : convertRawProp(context, rawProps, "draggedTypes", sourceProps.draggedTypes, {})), - tooltip( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.tooltip - : convertRawProp(context, rawProps, "tooltip", sourceProps.tooltip, {})), - cursor( - CoreFeatures::enablePropIteratorSetter - ? sourceProps.cursor - : convertRawProp(context, rawProps, "cursor", sourceProps.cursor, {})){}; - -#define VIEW_EVENT_CASE_MACOS(eventType, eventString) \ - case CONSTEXPR_RAW_PROPS_KEY_HASH(eventString): { \ - MacOSViewEvents defaultViewEvents{}; \ - bool res = defaultViewEvents[eventType]; \ - if (value.hasValue()) { \ - fromRawValue(context, value, res); \ - } \ - macOSViewEvents[eventType] = res; \ - return; \ - } - -void MacOSViewProps::setProp( - const PropsParserContext &context, - RawPropsPropNameHash hash, - const char *propName, - RawValue const &value) { - switch (hash) { - VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyDown, "onKeyDown"); - VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyUp, "onKeyUp"); - VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseEnter, "onMouseEnter"); - VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseLeave, "onMouseLeave"); - VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::DoubleClick, "onDoubleClick"); - RAW_SET_PROP_SWITCH_CASE_BASIC(focusable, false); - RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing, true); - RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown, {}); - RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysUp, {}); - RAW_SET_PROP_SWITCH_CASE_BASIC(draggedTypes, {}); - RAW_SET_PROP_SWITCH_CASE_BASIC(tooltip, {}); - RAW_SET_PROP_SWITCH_CASE_BASIC(cursor, {}); - } -} - -} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h b/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h deleted file mode 100644 index 1e153a5a3b8b46..00000000000000 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewProps.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include - -namespace facebook::react { - -class MacOSViewProps { - public: - MacOSViewProps() = default; - MacOSViewProps( - const PropsParserContext &context, - const MacOSViewProps &sourceProps, - const RawProps &rawProps, - bool shouldSetRawProps = true); - - void - setProp( - const PropsParserContext &context, - RawPropsPropNameHash hash, - const char *propName, - RawValue const &value); - - MacOSViewEvents macOSViewEvents{}; - - bool focusable{false}; - bool enableFocusRing{true}; - - std::optional> validKeysDown{}; - std::optional> validKeysUp{}; - - std::optional> draggedTypes{}; - - std::optional tooltip{}; - std::optional cursor{}; -}; - -} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/conversions.h b/ReactCommon/react/renderer/components/view/macOS/conversions.h deleted file mode 100644 index 18c3bfde5b5c7c..00000000000000 --- a/ReactCommon/react/renderer/components/view/macOS/conversions.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -namespace facebook::react { - -static inline MacOSViewEvents convertRawProp( - const PropsParserContext &context, - const RawProps &rawProps, - const MacOSViewEvents &sourceValue, - const MacOSViewEvents &defaultValue) { - MacOSViewEvents result{}; - using Offset = MacOSViewEvents::Offset; - - result[Offset::KeyDown] = - convertRawProp(context, rawProps, "onKeyDown", sourceValue[Offset::KeyDown], defaultValue[Offset::KeyDown]); - result[Offset::KeyUp] = - convertRawProp(context, rawProps, "onKeyUp", sourceValue[Offset::KeyUp], defaultValue[Offset::KeyUp]); - - result[Offset::MouseEnter] = - convertRawProp(context, rawProps, "onMouseEnter", sourceValue[Offset::MouseEnter], defaultValue[Offset::MouseEnter]); - result[Offset::MouseLeave] = - convertRawProp(context, rawProps, "onMouseLeave", sourceValue[Offset::MouseLeave], defaultValue[Offset::MouseLeave]); - - result[Offset::DoubleClick] = - convertRawProp(context, rawProps, "onDoubleClick", sourceValue[Offset::DoubleClick], defaultValue[Offset::DoubleClick]); - - return result; -} - -inline void fromRawValue(const PropsParserContext &context, const RawValue &value, HandledKey &result) { - if (value.hasType>()) { - auto map = static_cast>(value); - for (const auto &pair : map) { - if (pair.first == "key") { - result.key = static_cast(pair.second); - } else if (pair.first == "altKey") { - result.altKey = static_cast(pair.second); - } else if (pair.first == "ctrlKey") { - result.ctrlKey = static_cast(pair.second); - } else if (pair.first == "shiftKey") { - result.shiftKey = static_cast(pair.second); - } else if (pair.first == "metaKey") { - result.metaKey = static_cast(pair.second); - } - } - } else if (value.hasType()) { - result.key = (std::string)value; - } -} - -inline void fromRawValue(const PropsParserContext &context, const RawValue &value, DraggedType &result) { - auto string = (std::string)value; - if (string == "fileUrl") { - result = DraggedType::FileUrl; - } else if (string == "image") { - result = DraggedType::Image; - } else if (string == "string") { - result = DraggedType::String; - } else { - abort(); - } -} - -inline void fromRawValue(const PropsParserContext &context, const RawValue &value, Cursor &result) { - auto string = (std::string)value; - if (string == "alias") { - result = Cursor::DragLink; - } else if (string == "auto") { - result = Cursor::Auto; - } else if (string == "col-resize") { - result = Cursor::ResizeLeftRight; - } else if (string == "context-menu") { - result = Cursor::ContextualMenu; - } else if (string == "copy") { - result = Cursor::DragCopy; - } else if (string == "crosshair") { - result = Cursor::Crosshair; - } else if (string == "default") { - result = Cursor::Arrow; - } else if (string == "disappearing-item") { - result = Cursor::DisappearingItem; - } else if (string == "e-resize") { - result = Cursor::ResizeRight; - } else if (string == "grab") { - result = Cursor::OpenHand; - } else if (string == "grabbing") { - result = Cursor::ClosedHand; - } else if (string == "n-resize") { - result = Cursor::ResizeUp; - } else if (string == "no-drop") { - result = Cursor::OperationNotAllowed; - } else if (string == "not-allowed") { - result = Cursor::OperationNotAllowed; - } else if (string == "pointer") { - result = Cursor::PointingHand; - } else if (string == "row-resize") { - result = Cursor::ResizeUpDown; - } else if (string == "s-resize") { - result = Cursor::ResizeDown; - } else if (string == "text") { - result = Cursor::IBeam; - } else if (string == "vertical-text") { - result = Cursor::IBeamCursorForVerticalLayout; - } else if (string == "w-resize") { - result = Cursor::ResizeLeft; - } else { - result = Cursor::Auto; - } -} - -} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/primitives.h b/ReactCommon/react/renderer/components/view/macOS/primitives.h deleted file mode 100644 index 4dae459b55f1d3..00000000000000 --- a/ReactCommon/react/renderer/components/view/macOS/primitives.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -namespace facebook::react { - -enum class DraggedType { - FileUrl, - Image, - String, -}; - -enum class Cursor { - Arrow, - Auto, - ClosedHand, - ContextualMenu, - Crosshair, - DisappearingItem, - DragCopy, - DragLink, - IBeam, - IBeamCursorForVerticalLayout, - OpenHand, - OperationNotAllowed, - PointingHand, - ResizeDown, - ResizeLeft, - ResizeLeftRight, - ResizeRight, - ResizeUp, - ResizeUpDown, -}; - -struct MacOSViewEvents { - std::bitset<8> bits{}; - - enum class Offset : uint8_t { - // Keyboard Events - KeyDown = 1, - KeyUp = 2, - - // Mouse Events - MouseEnter = 3, - MouseLeave = 4, - DoubleClick = 5, - }; - - constexpr bool operator[](const Offset offset) const { - return bits[static_cast(offset)]; - } - - std::bitset<8>::reference operator[](const Offset offset) { - return bits[static_cast(offset)]; - } -}; - -inline static bool operator==(MacOSViewEvents const &lhs, MacOSViewEvents const &rhs) { - return lhs.bits == rhs.bits; -} - -inline static bool operator!=(MacOSViewEvents const &lhs, MacOSViewEvents const &rhs) { - return lhs.bits != rhs.bits; -} - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 153008c3228fe0..b0c086b4d84fc0 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -219,7 +219,7 @@ Pod::Spec.new do |s| sss.dependency "Yoga" sss.compiler_flags = folly_compiler_flags sss.source_files = "react/renderer/components/view/**/*.{m,mm,cpp,h}" - sss.exclude_files = "react/renderer/components/view/tests", "react/renderer/components/view/platform/android" + sss.exclude_files = "react/renderer/components/view/tests", "react/renderer/components/view/platform/android", "react/renderer/components/view/platform/cxx" sss.header_dir = "react/renderer/components/view" sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/Yoga\"" } end diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h index 2ca5038beeaf7e..aeb3413027cc0d 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputEventEmitter.h @@ -12,7 +12,7 @@ namespace facebook::react { #if TARGET_OS_OSX // [macOS -#include +#include #endif // macOS] class TextInputMetrics { diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h index ad86ce6496cf53..93c87eebb2a6bc 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/primitives.h @@ -9,6 +9,7 @@ #include #include +#include namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h index 1aa0757db94317..fc614729cc9c08 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/propsConversions.h @@ -198,12 +198,13 @@ inline void fromRawValue( } else { LOG(ERROR) << "Unsupported Selection type"; } +} #if TARGET_OS_OSX // [macOS static inline void fromRawValue( - const PropsParserContext &context, - const RawValue &value, - SubmitKeyEvent &result) { + const PropsParserContext& context, + const RawValue& value, + SubmitKeyEvent& result) { auto map = (std::unordered_map)value; auto tmp_key = map.find("key"); @@ -233,9 +234,9 @@ static inline void fromRawValue( } static inline void fromRawValue( - const PropsParserContext &context, - const RawValue &value, - std::vector &result) { + const PropsParserContext& context, + const RawValue& value, + std::vector& result) { auto items = (std::vector)value; for (const auto &item : items) { SubmitKeyEvent newItem; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h index 1bd62d95a09703..584bc01239ce44 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewEventEmitter.h @@ -13,14 +13,13 @@ #include #include -#include "HostPlatformViewEventEmitter.h" +#include "TouchEventEmitter.h" namespace facebook::react { -class BaseViewEventEmitter : public HostPlatformViewEventEmitter { +class BaseViewEventEmitter : public TouchEventEmitter { public: - using HostPlatformViewEventEmitter::HostPlatformViewEventEmitter; - + using TouchEventEmitter::TouchEventEmitter; #pragma mark - Accessibility diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index e14d0d82abec2a..59ab0732b4e9ee 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -54,7 +54,6 @@ BaseViewProps::BaseViewProps( const RawProps& rawProps) : YogaStylableProps(context, sourceProps, rawProps), AccessibilityProps(context, sourceProps, rawProps), - HostPlatformViewProps(context, sourceProps, rawProps, shouldSetRawProps), opacity( CoreFeatures::enablePropIteratorSetter ? sourceProps.opacity : convertRawProp( @@ -239,7 +238,6 @@ BaseViewProps::BaseViewProps( "removeClippedSubviews", sourceProps.removeClippedSubviews, false)), - experimental_layoutConformance( CoreFeatures::enablePropIteratorSetter ? sourceProps.experimental_layoutConformance @@ -272,7 +270,6 @@ void BaseViewProps::setProp( // reuse the same values. YogaStylableProps::setProp(context, hash, propName, value); AccessibilityProps::setProp(context, hash, propName, value); - HostPlatformViewProps::setProp(context, hash, propName, value); static auto defaults = BaseViewProps{}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index 995c455b5e784b..9e8d56f6d50a5a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -21,9 +20,8 @@ namespace facebook::react { -class BaseViewProps : public YogaStylableProps, public AccessibilityProps, public HostPlatformViewProps { +class BaseViewProps : public YogaStylableProps, public AccessibilityProps { public: - using SharedViewProps = std::shared_ptr; BaseViewProps() = default; BaseViewProps( const PropsParserContext& context, diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h deleted file mode 100644 index ba323a7928efdd..00000000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewEventEmitter.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#if TARGET_OS_OSX - -#include - -namespace facebook::react { -using HostPlatformViewEventEmitter = MacOSViewEventEmitter; -} // namespace facebook::react - -#else - -#include - -namespace facebook::react { - using HostPlatformViewEventEmitter = TouchEventEmitter; -} // namespace facebook::react - -#endif diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h deleted file mode 100644 index 36f00723ffcf30..00000000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/view/HostPlatformViewProps.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#if !TARGET_OS_OSX // [macOS -#include -#include -#else // [macOS -#include -#endif // macOS] - -namespace facebook::react { -#if !TARGET_OS_OSX // [macOS] -class HostPlatformViewProps { - public: - HostPlatformViewProps() = default; - HostPlatformViewProps( - const PropsParserContext &context, - const HostPlatformViewProps &sourceProps, - const RawProps &rawProps, - bool shouldSetRawProps = true) {} - - void - setProp( - const PropsParserContext &context, - RawPropsPropNameHash hash, - const char *propName, - RawValue const &value) {} -}; -#else // [macOS - using HostPlatformViewProps = MacOSViewProps; -#endif // macOS] -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/DraggedType.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/DraggedType.h new file mode 100644 index 00000000000000..5c47eb918b45b1 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/DraggedType.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + +#pragma once + +namespace facebook::react { + +enum class DraggedType { + FileUrl, + Image, + String, +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformTouch.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformTouch.h new file mode 100644 index 00000000000000..0d441117751c89 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformTouch.h @@ -0,0 +1,14 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { +using HostPlatformTouch = BaseTouch; +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp similarity index 80% rename from ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp rename to packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp index 032b3350aabdd0..b2a23d6250e9df 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -#include "MacOSViewEventEmitter.h" +#include "HostPlatformViewEventEmitter.h" namespace facebook::react { @@ -25,21 +25,20 @@ static jsi::Value keyEventPayload(jsi::Runtime &runtime, KeyEvent const &event) return payload; }; -void MacOSViewEventEmitter::onKeyDown(KeyEvent const &keyEvent) const { +void HostPlatformViewEventEmitter::onKeyDown(KeyEvent const &keyEvent) const { dispatchEvent( "keyDown", [keyEvent](jsi::Runtime &runtime) { return keyEventPayload(runtime, keyEvent); }, EventPriority::AsynchronousBatched); } -void MacOSViewEventEmitter::onKeyUp(KeyEvent const &keyEvent) const { +void HostPlatformViewEventEmitter::onKeyUp(KeyEvent const &keyEvent) const { dispatchEvent( "keyUp", [keyEvent](jsi::Runtime &runtime) { return keyEventPayload(runtime, keyEvent); }, EventPriority::AsynchronousBatched); } - #pragma mark - Mouse Events static jsi::Object mouseEventPayload(jsi::Runtime &runtime, MouseEvent const &event) { @@ -55,31 +54,30 @@ static jsi::Object mouseEventPayload(jsi::Runtime &runtime, MouseEvent const &ev return payload; }; -void MacOSViewEventEmitter::onMouseEnter(MouseEvent const &mouseEvent) const { +void HostPlatformViewEventEmitter::onMouseEnter(MouseEvent const &mouseEvent) const { dispatchEvent( "mouseEnter", [mouseEvent](jsi::Runtime &runtime) { return mouseEventPayload(runtime, mouseEvent); }, EventPriority::AsynchronousBatched); } -void MacOSViewEventEmitter::onMouseLeave(MouseEvent const &mouseEvent) const { +void HostPlatformViewEventEmitter::onMouseLeave(MouseEvent const &mouseEvent) const { dispatchEvent( "mouseLeave", [mouseEvent](jsi::Runtime &runtime) { return mouseEventPayload(runtime, mouseEvent); }, EventPriority::AsynchronousBatched); } -void MacOSViewEventEmitter::onDoubleClick(MouseEvent const &mouseEvent) const { +void HostPlatformViewEventEmitter::onDoubleClick(MouseEvent const &mouseEvent) const { dispatchEvent( "doubleClick", [mouseEvent](jsi::Runtime &runtime) { return mouseEventPayload(runtime, mouseEvent); }, EventPriority::AsynchronousBatched); } - #pragma mark - Drag and Drop Events -jsi::Value MacOSViewEventEmitter::dataTransferPayload(jsi::Runtime &runtime, std::vector const &dataTransferItems) { +jsi::Value HostPlatformViewEventEmitter::dataTransferPayload(jsi::Runtime &runtime, std::vector const &dataTransferItems) { auto filesArray = jsi::Array(runtime, dataTransferItems.size()); auto itemsArray = jsi::Array(runtime, dataTransferItems.size()); auto typesArray = jsi::Array(runtime, dataTransferItems.size()); @@ -99,60 +97,59 @@ jsi::Value MacOSViewEventEmitter::dataTransferPayload(jsi::Runtime &runtime, std 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, DragEvent const &event) { auto payload = mouseEventPayload(runtime, event); - auto dataTransferObject = MacOSViewEventEmitter::dataTransferPayload(runtime, event.dataTransferItems); + auto dataTransferObject = HostPlatformViewEventEmitter::dataTransferPayload(runtime, event.dataTransferItems); payload.setProperty(runtime, "dataTransfer", dataTransferObject); return payload; } -void MacOSViewEventEmitter::onDragEnter(DragEvent const &dragEvent) const { +void HostPlatformViewEventEmitter::onDragEnter(DragEvent const &dragEvent) const { dispatchEvent( "dragEnter", [dragEvent](jsi::Runtime &runtime) { return dragEventPayload(runtime, dragEvent); }, EventPriority::AsynchronousBatched); } -void MacOSViewEventEmitter::onDragLeave(DragEvent const &dragEvent) const { +void HostPlatformViewEventEmitter::onDragLeave(DragEvent const &dragEvent) const { dispatchEvent( "dragLeave", [dragEvent](jsi::Runtime &runtime) { return dragEventPayload(runtime, dragEvent); }, EventPriority::AsynchronousBatched); } -void MacOSViewEventEmitter::onDrop(DragEvent const &dragEvent) const { +void HostPlatformViewEventEmitter::onDrop(DragEvent const &dragEvent) const { dispatchEvent( "drop", [dragEvent](jsi::Runtime &runtime) { return dragEventPayload(runtime, dragEvent); }, EventPriority::AsynchronousBatched); } - #pragma mark - Focus Events -void MacOSViewEventEmitter::onFocus() const { +void HostPlatformViewEventEmitter::onFocus() const { dispatchEvent("focus"); } -void MacOSViewEventEmitter::onBlur() const { +void HostPlatformViewEventEmitter::onBlur() const { dispatchEvent("blur"); } diff --git a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h similarity index 75% rename from ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h rename to packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h index bda6a1bb82f639..c6fbacea312e4b 100644 --- a/ReactCommon/react/renderer/components/view/macOS/MacOSViewEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h @@ -1,45 +1,45 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include - -namespace facebook::react { - -class MacOSViewEventEmitter : public TouchEventEmitter { - public: - using TouchEventEmitter::TouchEventEmitter; - -#pragma mark - Keyboard Events - - void onKeyDown(KeyEvent const &keyEvent) const; - void onKeyUp(KeyEvent const &keyEvent) const; - -#pragma mark - Mouse Events - - void onMouseEnter(MouseEvent const &mouseEvent) const; - void onMouseLeave(MouseEvent const &mouseEvent) const; - void onDoubleClick(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; - -#pragma mark - Focus Events - - void onFocus() const; - void onBlur() const; - - static jsi::Value dataTransferPayload(jsi::Runtime &runtime, std::vector const &dataTransferItems); -}; - -} // namespace facebook::react +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +class HostPlatformViewEventEmitter : public BaseViewEventEmitter { + public: + using BaseViewEventEmitter::BaseViewEventEmitter; + +#pragma mark - Keyboard Events + + void onKeyDown(KeyEvent const &keyEvent) const; + void onKeyUp(KeyEvent const &keyEvent) const; + +#pragma mark - Mouse Events + + void onMouseEnter(MouseEvent const &mouseEvent) const; + void onMouseLeave(MouseEvent const &mouseEvent) const; + void onDoubleClick(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; + +#pragma mark - Focus Events + + void onFocus() const; + void onBlur() const; + + static jsi::Value dataTransferPayload(jsi::Runtime &runtime, std::vector const &dataTransferItems); +}; + +} // namespace facebook::react 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 new file mode 100644 index 00000000000000..3f62b9b74a6b5f --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "HostPlatformViewProps.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace facebook::react { + +HostPlatformViewProps::HostPlatformViewProps( + const PropsParserContext& context, + const HostPlatformViewProps& sourceProps, + const RawProps& rawProps) + : BaseViewProps(context, sourceProps, rawProps), + macOSViewEvents( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.macOSViewEvents + : convertRawProp(context, rawProps, sourceProps.macOSViewEvents, {})), + focusable( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.focusable + : convertRawProp(context, rawProps, "focusable", sourceProps.focusable, {})), + enableFocusRing( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.enableFocusRing + : convertRawProp(context, rawProps, "enableFocusRing", sourceProps.enableFocusRing, true)), + validKeysDown( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.validKeysDown + : convertRawProp(context, rawProps, "validKeysDown", sourceProps.validKeysDown, {})), + validKeysUp( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.validKeysUp + : convertRawProp(context, rawProps, "validKeysUp", sourceProps.validKeysUp, {})), + draggedTypes( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.draggedTypes + : convertRawProp(context, rawProps, "draggedTypes", sourceProps.draggedTypes, {})), + tooltip( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.tooltip + : convertRawProp(context, rawProps, "tooltip", sourceProps.tooltip, {})){}; + +#define VIEW_EVENT_CASE_MACOS(eventType, eventString) \ + case CONSTEXPR_RAW_PROPS_KEY_HASH(eventString): { \ + MacOSViewEvents defaultViewEvents{}; \ + bool res = defaultViewEvents[eventType]; \ + if (value.hasValue()) { \ + fromRawValue(context, value, res); \ + } \ + macOSViewEvents[eventType] = res; \ + return; \ + } + +void HostPlatformViewProps::setProp( + const PropsParserContext& context, + RawPropsPropNameHash hash, + const char* propName, + RawValue const& value) { + // All Props structs setProp methods must always, unconditionally, + // call all super::setProp methods, since multiple structs may + // reuse the same values. + BaseViewProps::setProp(context, hash, propName, value); + + static auto defaults = HostPlatformViewProps{}; + + switch (hash) { + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyDown, "onKeyDown"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::KeyUp, "onKeyUp"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseEnter, "onMouseEnter"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::MouseLeave, "onMouseLeave"); + VIEW_EVENT_CASE_MACOS(MacOSViewEvents::Offset::DoubleClick, "onDoubleClick"); + RAW_SET_PROP_SWITCH_CASE_BASIC(focusable); + RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing); + RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysDown); + RAW_SET_PROP_SWITCH_CASE_BASIC(validKeysUp); + RAW_SET_PROP_SWITCH_CASE_BASIC(draggedTypes); + RAW_SET_PROP_SWITCH_CASE_BASIC(tooltip); + RAW_SET_PROP_SWITCH_CASE_BASIC(cursor); + } +} + +inline void fromRawValue(const PropsParserContext &context, const RawValue &value, HandledKey &result) { + if (value.hasType>()) { + auto map = static_cast>(value); + for (const auto &pair : map) { + if (pair.first == "key") { + result.key = static_cast(pair.second); + } else if (pair.first == "altKey") { + result.altKey = static_cast(pair.second); + } else if (pair.first == "ctrlKey") { + result.ctrlKey = static_cast(pair.second); + } else if (pair.first == "shiftKey") { + result.shiftKey = static_cast(pair.second); + } else if (pair.first == "metaKey") { + result.metaKey = static_cast(pair.second); + } + } + } else if (value.hasType()) { + result.key = (std::string)value; + } +} + +inline void fromRawValue(const PropsParserContext &context, const RawValue &value, DraggedType &result) { + auto string = (std::string)value; + if (string == "fileUrl") { + result = DraggedType::FileUrl; + } else if (string == "image") { + result = DraggedType::Image; + } else if (string == "string") { + result = DraggedType::String; + } else { + abort(); + } +} + + +} // namespace facebook::react 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 new file mode 100644 index 00000000000000..2a39064a90374c --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook::react { +class HostPlatformViewProps : public BaseViewProps { + public: + HostPlatformViewProps() = default; + HostPlatformViewProps( + const PropsParserContext &context, + const HostPlatformViewProps &sourceProps, + const RawProps &rawProps); + + void setProp( + const PropsParserContext& context, + RawPropsPropNameHash hash, + const char* propName, + const RawValue& value); + + MacOSViewEvents macOSViewEvents{}; + +#pragma mark - Props + + bool focusable{false}; + bool enableFocusRing{true}; + + std::optional> validKeysDown{}; + std::optional> validKeysUp{}; + + std::optional> draggedTypes{}; + + std::optional tooltip{}; +}; +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewTraitsInitializer.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewTraitsInitializer.h new file mode 100644 index 00000000000000..c9d8c782ad1bc5 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewTraitsInitializer.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace facebook::react::HostPlatformViewTraitsInitializer { + +inline bool formsStackingContext(const ViewProps& props) { + return false; +} + +inline bool formsView(const ViewProps& props) { + return false; +} + +inline ShadowNodeTraits::Trait extraTraits() { + return ShadowNodeTraits::Trait::None; +} + +} // namespace facebook::react::HostPlatformViewTraitsInitializer diff --git a/ReactCommon/react/renderer/components/view/macOS/KeyEvent.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/KeyEvent.h similarity index 100% rename from ReactCommon/react/renderer/components/view/macOS/KeyEvent.h rename to packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/KeyEvent.h diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MacOSViewEvents.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MacOSViewEvents.h new file mode 100644 index 00000000000000..a3506a41d75e30 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MacOSViewEvents.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook::react { + +struct MacOSViewEvents { + std::bitset<8> bits{}; + + enum class Offset : uint8_t { + // Keyboard Events + KeyDown = 1, + KeyUp = 2, + + // Mouse Events + MouseEnter = 3, + MouseLeave = 4, + DoubleClick = 5, + }; + + constexpr bool operator[](const Offset offset) const { + return bits[static_cast(offset)]; + } + + std::bitset<8>::reference operator[](const Offset offset) { + return bits[static_cast(offset)]; + } +}; + +inline static bool operator==(MacOSViewEvents const &lhs, MacOSViewEvents const &rhs) { + return lhs.bits == rhs.bits; +} + +inline static bool operator!=(MacOSViewEvents const &lhs, MacOSViewEvents const &rhs) { + return lhs.bits != rhs.bits; +} + +static inline MacOSViewEvents convertRawProp( + const PropsParserContext &context, + const RawProps &rawProps, + const MacOSViewEvents &sourceValue, + const MacOSViewEvents &defaultValue) { + MacOSViewEvents result{}; + using Offset = MacOSViewEvents::Offset; + + // Key Events + result[Offset::KeyDown] = + convertRawProp(context, rawProps, "onKeyDown", sourceValue[Offset::KeyDown], defaultValue[Offset::KeyDown]); + result[Offset::KeyUp] = + convertRawProp(context, rawProps, "onKeyUp", sourceValue[Offset::KeyUp], defaultValue[Offset::KeyUp]); + + // Mouse Events + result[Offset::MouseEnter] = + convertRawProp(context, rawProps, "onMouseEnter", sourceValue[Offset::MouseEnter], defaultValue[Offset::MouseEnter]); + result[Offset::MouseLeave] = + convertRawProp(context, rawProps, "onMouseLeave", sourceValue[Offset::MouseLeave], defaultValue[Offset::MouseLeave]); + result[Offset::DoubleClick] = + convertRawProp(context, rawProps, "onDoubleClick", sourceValue[Offset::DoubleClick], defaultValue[Offset::DoubleClick]); + + return result; +} + +} // namespace facebook::react diff --git a/ReactCommon/react/renderer/components/view/macOS/MouseEvent.h b/packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MouseEvent.h similarity index 100% rename from ReactCommon/react/renderer/components/view/macOS/MouseEvent.h rename to packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/MouseEvent.h From fe4989ab7e2216f6dc0343e47a776549705e7313 Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Wed, 1 May 2024 14:43:50 -0700 Subject: [PATCH 79/80] [fabric] Revert to Core Cursor implementation --- .../View/RCTViewComponentView.mm | 124 ++++++------------ .../renderer/components/view/primitives.h | 21 ++- 2 files changed, 56 insertions(+), 89 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 7750cbebaa402d..8d18b7ee2822e8 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -32,71 +32,6 @@ using namespace facebook::react; -#if TARGET_OS_OSX // [macOS -inline NSCursor* RCTNSCursorFromCursor(facebook::react::Cursor cursorValue) { - NSCursor *cursor = nil; - - switch (cursorValue) { - case Cursor::Arrow: - cursor = [NSCursor arrowCursor]; - break; - case Cursor::ClosedHand: - cursor = [NSCursor closedHandCursor]; - break; - case Cursor::ContextualMenu: - cursor = [NSCursor contextualMenuCursor]; - break; - case Cursor::Crosshair: - cursor = [NSCursor crosshairCursor]; - break; - case Cursor::DisappearingItem: - cursor = [NSCursor disappearingItemCursor]; - break; - case Cursor::DragCopy: - cursor = [NSCursor dragCopyCursor]; - break; - case Cursor::DragLink: - cursor = [NSCursor dragLinkCursor]; - break; - case Cursor::IBeam: - cursor = [NSCursor IBeamCursor]; - break; - case Cursor::IBeamCursorForVerticalLayout: - cursor = [NSCursor IBeamCursorForVerticalLayout]; - break; - case Cursor::OpenHand: - cursor = [NSCursor openHandCursor]; - break; - case Cursor::OperationNotAllowed: - cursor = [NSCursor operationNotAllowedCursor]; - break; - case Cursor::PointingHand: - cursor = [NSCursor pointingHandCursor]; - break; - case Cursor::ResizeDown: - cursor = [NSCursor resizeDownCursor]; - break; - case Cursor::ResizeLeft: - cursor = [NSCursor resizeLeftCursor]; - break; - case Cursor::ResizeLeftRight: - cursor = [NSCursor resizeLeftRightCursor]; - break; - case Cursor::ResizeRight: - cursor = [NSCursor resizeRightCursor]; - break; - case Cursor::ResizeUp: - cursor = [NSCursor resizeUpCursor]; - break; - case Cursor::ResizeUpDown: - cursor = [NSCursor resizeUpDownCursor]; - break; - } - - return cursor; -} -#endif // macOS] - @implementation RCTViewComponentView { RCTUIColor *_backgroundColor; // [macOS] CALayer *_borderLayer; @@ -347,7 +282,11 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & // `cursor` if (oldViewProps.cursor != newViewProps.cursor) { - needsInvalidateLayer = YES; +#if !TARGET_OS_OSX // [macOS] + needsInvalidateLayer = YES; // `cursor` +#else // [macOS + _cursor = NSCursorFromCursor(newViewProps.cursor); +#endif // macOS] } // `shouldRasterize` @@ -544,13 +483,6 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & } } - // `cursor` - if (oldViewProps.cursor != newViewProps.cursor) { - _cursor = nil; - if (newViewProps.cursor.has_value()) { - _cursor = RCTNSCursorFromCursor(newViewProps.cursor.value()); - } - } #endif // macOS] _needsInvalidateLayer = _needsInvalidateLayer || needsInvalidateLayer; @@ -753,13 +685,17 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) } #if TARGET_OS_OSX // [macOS - static NSCursor *NSCursorFromCursor(Cursor cursor) +static NSCursor *NSCursorFromCursor(Cursor cursor) { switch (cursor) { - case Cursor::Auto: - return [NSCursor arrowCursor]; case Cursor::Alias: return [NSCursor dragLinkCursor]; + case Cursor::Arrow: + return [NSCursor arrowCursor]; + case Cursor::Auto: + return [NSCursor arrowCursor]; + case Cursor::ClosedHand: + return [NSCursor closedHandCursor]; case Cursor::ColumnResize: return [NSCursor resizeLeftRightCursor]; case Cursor::ContextualMenu: @@ -772,20 +708,34 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) return [NSCursor arrowCursor]; case Cursor::DisappearingItem: return [NSCursor disappearingItemCursor]; + case Cursor::DragCopy: + return [NSCursor dragCopyCursor]; + case Cursor::DragLink: + return [NSCursor dragLinkCursor]; case Cursor::EastResize: return [NSCursor resizeRightCursor]; case Cursor::Grab: return [NSCursor openHandCursor]; case Cursor::Grabbing: return [NSCursor closedHandCursor]; + case Cursor::IBeam: + return [NSCursor IBeamCursor]; + case Cursor::IBeamCursorForVerticalLayout: + return [NSCursor IBeamCursorForVerticalLayout]; case Cursor::NorthResize: return [NSCursor resizeUpCursor]; case Cursor::NoDrop: return [NSCursor operationNotAllowedCursor]; case Cursor::NotAllowed: return [NSCursor operationNotAllowedCursor]; + case Cursor::OpenHand: + return [NSCursor openHandCursor]; + case Cursor::OperationNotAllowed: + return [NSCursor operationNotAllowedCursor]; case Cursor::Pointer: return [NSCursor pointingHandCursor]; + case Cursor::PointingHand: + return [NSCursor pointingHandCursor]; case Cursor::RowResize: return [NSCursor resizeUpDownCursor]; case Cursor::SouthResize: @@ -796,11 +746,22 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) return [NSCursor IBeamCursorForVerticalLayout]; case Cursor::WestResize: return [NSCursor resizeLeftCursor]; + case Cursor::ResizeDown: + return [NSCursor resizeDownCursor]; + case Cursor::ResizeLeft: + return [NSCursor resizeLeftCursor]; + case Cursor::ResizeLeftRight: + return [NSCursor resizeLeftRightCursor]; + case Cursor::ResizeRight: + return [NSCursor resizeRightCursor]; + case Cursor::ResizeUp: + return [NSCursor resizeUpCursor]; + case Cursor::ResizeUpDown: + return [NSCursor resizeDownCursor]; } } #endif // macOS] - - (void)invalidateLayer { CALayer *layer = self.layer; @@ -1503,15 +1464,6 @@ - (void)viewDidMoveToWindow [super viewDidMoveToWindow]; } -- (void)resetCursorRects -{ - [self discardCursorRects]; - if (_cursor) { - [self addCursorRect:self.bounds cursor:_cursor]; - } - [self updateMouseOverIfNeeded]; -} - - (void)updateTrackingAreas { if (_trackingArea) { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h index b97035e7cde480..a1eab3f86e3015 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -92,22 +92,37 @@ enum class BorderCurve : uint8_t { Circular, Continuous }; enum class BorderStyle : uint8_t { Solid, Dotted, Dashed }; // [macOS [visionOS] -enum class Cursor : uint8_t { - Auto, +enum class Cursor : uint8_t { Alias, + Arrow, + Auto, + ClosedHand, ColumnResize, ContextualMenu, Copy, Crosshair, Default, DisappearingItem, + DragCopy, + DragLink, EastResize, Grab, Grabbing, - NorthResize, + IBeam, + IBeamCursorForVerticalLayout, NoDrop, + NorthResize, NotAllowed, + OpenHand, + OperationNotAllowed, Pointer, + PointingHand, + ResizeDown, + ResizeLeft, + ResizeLeftRight, + ResizeRight, + ResizeUp, + ResizeUpDown, RowResize, SouthResize, Text, From 6b0fd9c4b1a9f4042908f922f2f8c14118c8c65c Mon Sep 17 00:00:00 2001 From: Shawn Dempsey Date: Wed, 1 May 2024 13:44:46 -0700 Subject: [PATCH 80/80] [fabric] Fix warning & formatting --- .../Libraries/Text/TextInput/Singleline/RCTUITextField.h | 4 ++-- .../Libraries/Text/TextInput/Singleline/RCTUITextField.mm | 2 ++ .../RCTLegacyViewManagerInteropComponentView.mm | 4 ---- .../ComponentViews/Text/RCTParagraphComponentView.mm | 8 +++----- .../Mounting/ComponentViews/TextInput/RCTTextInputUtils.h | 2 -- .../ComponentViews/TextInput/RCTTextInputUtils.mm | 2 -- 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h index ad699abed9f7bd..c085e78eca23a2 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h @@ -45,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN #if !TARGET_OS_OSX // [macOS] @property (nonatomic, assign, getter=isEditable) BOOL editable; #else // [macOS -@property (assign, getter=isEditable) BOOL editable; +@property (atomic, assign, getter=isEditable) BOOL editable; #endif // macOS] @property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled; @property (nonatomic, strong, nullable) NSString *inputAccessoryViewID; @@ -56,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_OSX // [macOS @property (nonatomic, copy, nullable) NSString *text; @property (nonatomic, copy, nullable) NSAttributedString *attributedText; -@property (nonatomic, copy) NSDictionary *defaultTextAttributes; +@property (nonatomic, strong, nullable) NSDictionary *defaultTextAttributes; @property (nonatomic, assign) NSTextAlignment textAlignment; @property (nonatomic, getter=isAutomaticTextReplacementEnabled) BOOL automaticTextReplacementEnabled; @property (nonatomic, getter=isAutomaticSpellingCorrectionEnabled) BOOL automaticSpellingCorrectionEnabled; diff --git a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm index dd2eb55ab5f123..112436a7a85ad0 100644 --- a/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm +++ b/packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm @@ -93,7 +93,9 @@ @implementation RCTUITextField { #endif RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter; NSDictionary *_defaultTextAttributes; +#if TARGET_OS_OSX // [macOS NSArray *_readablePasteboardTypes; +#endif // macOS] } #if TARGET_OS_OSX // [macOS diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm index 398c8c8da01dfd..3a25c02b2e7fb5 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/LegacyViewManagerInterop/RCTLegacyViewManagerInteropComponentView.mm @@ -15,10 +15,6 @@ #import #import "RCTLegacyViewManagerInteropCoordinatorAdapter.h" -#if TARGET_OS_OSX // [macOS -#import -#endif // macOS] - using namespace facebook::react; static NSString *const kRCTLegacyInteropChildComponentKey = @"childComponentView"; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 7f19346cf04b8d..f30dd1c7f5f002 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -50,9 +50,6 @@ - (BOOL)resignFirstResponder return [super resignFirstResponder]; } -@end - -@interface RCTParagraphComponentView () @end #endif // macOS] @@ -67,11 +64,12 @@ @implementation RCTParagraphComponentView { ParagraphShadowNode::ConcreteState::Shared _state; ParagraphAttributes _paragraphAttributes; RCTParagraphComponentAccessibilityProvider *_accessibilityProvider; - UILongPressGestureRecognizer *_longPressGestureRecognizer; - CAShapeLayer *_highlightLayer; } #else // [macOS +@interface RCTParagraphComponentView () +@end + @implementation RCTParagraphComponentView { ParagraphShadowNode::ConcreteState::Shared _state; ParagraphAttributes _paragraphAttributes; diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h index 337ee6deb19a75..906e1b47b0faa9 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.h @@ -35,9 +35,7 @@ UITextAutocapitalizationType RCTUITextAutocapitalizationTypeFromAutocapitalizati UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance( facebook::react::KeyboardAppearance keyboardAppearance); -#endif -#if !TARGET_OS_OSX // [macOS] UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional spellCheck); UITextFieldViewMode RCTUITextFieldViewModeFromTextInputAccessoryVisibilityMode( diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm index 9ad02214e9fea9..c890d602822bed 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputUtils.mm @@ -107,9 +107,7 @@ UIKeyboardAppearance RCTUIKeyboardAppearanceFromKeyboardAppearance(KeyboardAppea return UIKeyboardAppearanceDark; } } -#endif -#if !TARGET_OS_OSX // [macOS] UITextSpellCheckingType RCTUITextSpellCheckingTypeFromOptionalBool(std::optional spellCheck) { return spellCheck.has_value() ? (*spellCheck ? UITextSpellCheckingTypeYes : UITextSpellCheckingTypeNo)