Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/macos-fabric-dev-menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rnx-kit/react-native-host": patch
---

Wire up the dev menu on macOS Fabric root views so secondary-click shows it. On react-native-macos 0.81+, this sets the new `RCTSurfaceHostingView.devMenu` property so upstream's `menuForEvent:` fires. On older versions, it installs a secondary-click gesture recognizer that pops up the dev menu directly. Without this, consumers of `host.viewWithModuleName:` lose the dev menu on the new architecture because they bypass `RCTRootViewFactory`, which is where upstream does the wiring.
78 changes: 71 additions & 7 deletions packages/react-native-host/cocoa/ReactNativeHost+View.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
#import <React/RCTRootView.h>
#endif // USE_FABRIC

#if defined(RCT_DEV_MENU) && RCT_DEV_MENU && __has_include(<React/RCTDevMenu.h>)
#import <React/RCTDevMenu.h>
#define RNX_WIRE_DEV_MENU 1
#endif

@implementation ReactNativeHost (View)

+ (instancetype)hostFromRootView:(RNXView *)rootView
Expand Down Expand Up @@ -52,37 +57,96 @@ - (RNXView *)viewWithModuleName:(NSString *)moduleName
initialProps[kReactConcurrentRoot] = @YES;
}

RNXView *rootView;
#if __has_include(<React/RCTFabricSurfaceHostingProxyRootView.h>)
return [[RCTFabricSurfaceHostingProxyRootView alloc] initWithBridge:self.bridge
moduleName:moduleName
initialProperties:initialProps];
rootView = [[RCTFabricSurfaceHostingProxyRootView alloc] initWithBridge:self.bridge
moduleName:moduleName
initialProperties:initialProps];
#elif USE_BRIDGELESS
RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName
initialProperties:initialProps];
#if __has_include(<react/renderer/graphics/LinearGradient.h>) // >=0.77
return [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
#else
// `-initWithSurface:` implicitly calls `start` and causes race conditions.
// This was fixed in 0.76.7, but for backwards compatibility, we should call
// `-initWithSurface:sizeMeasureMode` when possible. For more details, see
// https://github.com/facebook/react-native/pull/47313.
RCTSurfaceSizeMeasureMode sizeMeasureMode =
RCTSurfaceSizeMeasureModeWidthExact | RCTSurfaceSizeMeasureModeHeightExact;
return [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface
sizeMeasureMode:sizeMeasureMode];
rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface
sizeMeasureMode:sizeMeasureMode];
#endif // __has_include(<react/renderer/graphics/LinearGradient.h>)
#else // __has_include(<React/RCTFabricSurfaceHostingProxyRootView.h>)
RCTFabricSurface *surface =
[[RCTFabricSurface alloc] initWithSurfacePresenter:self.surfacePresenter
moduleName:moduleName
initialProperties:initialProps];
return [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
#endif // __has_include(<React/RCTFabricSurfaceHostingProxyRootView.h>)

#if defined(RNX_WIRE_DEV_MENU) && TARGET_OS_OSX
// react-native-macos 0.81+ exposes a `devMenu` property on
// `RCTSurfaceHostingView` whose `menuForEvent:` returns the dev menu on
// secondary click. Wire it up here so it actually fires; upstream's
// `RCTRootViewFactory` does the same, but consumers of this host bypass it.
if ([rootView respondsToSelector:@selector(setDevMenu:)]) {
[self usingModule:[RCTDevMenu class]
block:^(id<RCTBridgeModule> _Nullable module) {
if (module == nil) {
return;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[rootView performSelector:@selector(setDevMenu:) withObject:module];
#pragma clang diagnostic pop
}];
} else {
// Older react-native-macos versions don't override `menuForEvent:` on
// the Fabric root view, so secondary-click does nothing. Install a
// gesture recognizer that pops up the dev menu directly.
NSClickGestureRecognizer *recognizer = [[NSClickGestureRecognizer alloc]
initWithTarget:self
action:@selector(rnx_showFallbackDevMenu:)];
recognizer.buttonMask = 1 << 1; // Secondary (right) button
recognizer.numberOfClicksRequired = 1;
[rootView addGestureRecognizer:recognizer];
}
#endif // RNX_WIRE_DEV_MENU && TARGET_OS_OSX

return rootView;
#else
return [[RCTRootView alloc] initWithBridge:self.bridge
moduleName:moduleName
initialProperties:initialProperties];
#endif // USE_FABRIC
}

#if defined(RNX_WIRE_DEV_MENU) && TARGET_OS_OSX

- (void)rnx_showFallbackDevMenu:(NSGestureRecognizer *)recognizer
{
NSView *view = recognizer.view;
if (view == nil) {
return;
}
[self usingModule:[RCTDevMenu class]
block:^(id<RCTBridgeModule> _Nullable module) {
if (module == nil || ![module respondsToSelector:@selector(menu)]) {
return;
}
NSMenu *menu = [(RCTDevMenu *)module menu];
if (menu == nil) {
return;
}
NSEvent *event = NSApp.currentEvent;
if (event == nil) {
return;
}
[NSMenu popUpContextMenu:menu withEvent:event forView:view];
}];
}

#endif // RNX_WIRE_DEV_MENU && TARGET_OS_OSX

@end
Loading