diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm index fabfff326b854c..41bd41f44c4b60 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppSetupUtils.mm @@ -124,16 +124,6 @@ void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) // Necessary to allow NativeModules to lookup TurboModules [bridge setRCTTurboModuleRegistry:turboModuleManager]; -#if RCT_DEV - /** - * Instantiating DevMenu has the side-effect of registering - * shortcuts for CMD + d, CMD + i, and CMD + n via RCTDevMenu. - * Therefore, when TurboModules are enabled, we must manually create this - * NativeModule. - */ - [turboModuleManager moduleForName:"RCTDevMenu"]; -#endif // end RCT_DEV - auto runtimeInstallerLambda = [turboModuleManager, bridge, runtimeScheduler](facebook::jsi::Runtime &runtime) { if (!bridge || !turboModuleManager) { return; diff --git a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm index 266d6bab211cdb..be213a7801101b 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm @@ -34,6 +34,10 @@ #import #import +#if RCT_DEV_MENU +#import "RCTDevMenu.h" +#endif // RCT_DEV_MENU + @implementation RCTRootViewFactoryConfiguration - (instancetype)initWithBundleURL:(NSURL *)bundleURL newArchEnabled:(BOOL)newArchEnabled @@ -189,6 +193,12 @@ - (UIView *)viewWithModuleName:(NSString *)moduleName RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface]; +#if RCT_DEV_MENU + RCTDevMenu *devMenu = [self.reactHost.moduleRegistry moduleForClass:[RCTDevMenu class]]; + if (devMenu) { + surfaceHostingProxyRootView.devMenu = devMenu; + } +#endif // RCT_DEV_MENU surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor]; if (_configuration.customizeRootView != nil) { @@ -208,6 +218,16 @@ - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge { UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES); rootView.backgroundColor = [UIColor systemBackgroundColor]; + +#if RCT_DEV_MENU + if ([rootView isKindOfClass:[RCTSurfaceHostingView class]]) { + RCTDevMenu *devMenu = [bridge moduleForClass:[RCTDevMenu class]]; + if (devMenu) { + [(RCTSurfaceHostingView *)rootView setDevMenu:devMenu]; + } + } +#endif // RCT_DEV_MENU + return rootView; } diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h index 65accd5350665a..7f43dd54e03786 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h @@ -14,6 +14,7 @@ @class RCTBridge; @class RCTSurface; +@class RCTDevMenu; typedef UIView *_Nullable (^RCTSurfaceHostingViewActivityIndicatorViewFactory)(void); @@ -63,6 +64,14 @@ NS_ASSUME_NONNULL_BEGIN * @param disabled if `YES`, the auto-hide is disabled. Otherwise the loading view will be hidden automatically */ - (void)disableActivityIndicatorAutoHide:(BOOL)disabled; + +#if RCT_DEV_MENU +/** + * Dev menu for macOS context menu access. + */ +@property (nonatomic, strong, nullable) RCTDevMenu *devMenu; +#endif + @end NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm index 511e398a8bb78f..246efba1a22e7b 100644 --- a/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm +++ b/packages/react-native/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm @@ -13,6 +13,10 @@ #import "RCTSurfaceView.h" #import "RCTUtils.h" +#if RCT_DEV_MENU +#import "RCTDevMenu.h" +#endif // RCT_DEV_MENU + @interface RCTSurfaceHostingView () @property (nonatomic, assign) BOOL isActivityIndicatorViewVisible; @@ -126,6 +130,7 @@ - (void)setSizeMeasureMode:(RCTSurfaceSizeMeasureMode)sizeMeasureMode _sizeMeasureMode = sizeMeasureMode; [self _invalidateLayout]; } + - (void)disableActivityIndicatorAutoHide:(BOOL)disabled { _autoHideDisabled = disabled; @@ -249,4 +254,33 @@ - (void)surface:(__unused RCTSurface *)surface didChangeIntrinsicSize:(__unused }); } +#pragma mark - Dev Menu + +#if RCT_DEV_MENU +- (BOOL)canBecomeFirstResponder +{ + return YES; +} + +- (NSArray *)keyCommands +{ + return @[ + [UIKeyCommand keyCommandWithInput:@"d" + modifierFlags:UIKeyModifierCommand + action:@selector(toggle)], + [UIKeyCommand keyCommandWithInput:@"i" + modifierFlags:UIKeyModifierCommand + action:@selector(toggleElementInspector)], + ]; +} + +- (void)toggle { + [_devMenu toggle]; +} + +- (void)toggleElementInspector { + [_devMenu toggleElementInspector]; +} +#endif // RCT_DEV_MENU + @end diff --git a/packages/react-native/React/CoreModules/RCTDevMenu.h b/packages/react-native/React/CoreModules/RCTDevMenu.h index 976cb8c517b58e..acdf490782800e 100644 --- a/packages/react-native/React/CoreModules/RCTDevMenu.h +++ b/packages/react-native/React/CoreModules/RCTDevMenu.h @@ -57,7 +57,7 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification; /** * Whether the hotkeys that toggles the developer menu is enabled. */ -@property (nonatomic, assign) BOOL hotkeysEnabled; +@property (nonatomic, assign) BOOL hotkeysEnabled DEPRECATED_ATTRIBUTE; /** * Whether the developer menu is enabled. @@ -84,6 +84,11 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification; */ - (void)show; +/** + * Manually toggle the dev menu + */ +- (void)toggle; + /** * Deprecated, use `RCTReloadCommand` instead. */ @@ -105,6 +110,21 @@ RCT_EXTERN NSString *const RCTShowDevMenuNotification; */ - (void)disableReloadCommand; +/** + * Get the key commands for the dev menu (Cmd+D, Cmd+I, Cmd+R). + */ +- (NSArray *)keyCommands; + +/** + * Toggle the element inspector (called by key command). + */ +- (void)toggleElementInspector; + +/** + * Reload from key command (called by key command). + */ +- (void)reloadFromKeyCommand; + @end typedef NSString * (^RCTDevMenuItemTitleBlock)(void); diff --git a/packages/react-native/React/CoreModules/RCTDevMenu.mm b/packages/react-native/React/CoreModules/RCTDevMenu.mm index 890c1596df74f7..26816c3e60cc32 100644 --- a/packages/react-native/React/CoreModules/RCTDevMenu.mm +++ b/packages/react-native/React/CoreModules/RCTDevMenu.mm @@ -150,56 +150,10 @@ - (instancetype)init _keyboardShortcutsEnabled = true; _devMenuEnabled = true; - [self registerHotkeys]; } return self; } -- (void)registerHotkeys -{ -#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST - RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; - __weak __typeof(self) weakSelf = self; - - // Toggle debug menu - [commands registerKeyCommandWithInput:@"d" - modifierFlags:UIKeyModifierCommand - action:^(__unused UIKeyCommand *command) { - [weakSelf toggle]; - }]; - - // Toggle element inspector - [commands registerKeyCommandWithInput:@"i" - modifierFlags:UIKeyModifierCommand - action:^(__unused UIKeyCommand *command) { - [(RCTDevSettings *)[weakSelf.moduleRegistry moduleForName:"DevSettings"] - toggleElementInspector]; - }]; -#endif -} - -- (void)unregisterHotkeys -{ -#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST - RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; - - [commands unregisterKeyCommandWithInput:@"d" modifierFlags:UIKeyModifierCommand]; - [commands unregisterKeyCommandWithInput:@"i" modifierFlags:UIKeyModifierCommand]; -#endif -} - -- (BOOL)isHotkeysRegistered -{ -#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST - RCTKeyCommands *commands = [RCTKeyCommands sharedInstance]; - - return [commands isKeyCommandRegisteredForInput:@"d" modifierFlags:UIKeyModifierCommand] && - [commands isKeyCommandRegisteredForInput:@"i" modifierFlags:UIKeyModifierCommand]; -#else - return NO; -#endif -} - - (BOOL)isReloadCommandRegistered { #if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST @@ -523,25 +477,22 @@ - (BOOL)hotLoadingEnabled return ((RCTDevSettings *)[_moduleRegistry moduleForName:"DevSettings"]).isHotLoadingEnabled; } -- (void)setHotkeysEnabled:(BOOL)enabled +- (void)disableReloadCommand { - if (enabled) { - [self registerHotkeys]; - } else { - [self unregisterHotkeys]; + if ([self isReloadCommandRegistered]) { + [self unregisterReloadCommand]; } } -- (BOOL)hotkeysEnabled +- (void)toggleElementInspector { - return [self isHotkeysRegistered]; + RCTDevSettings *devSettings = [_moduleRegistry moduleForName:"DevSettings"]; + [devSettings toggleElementInspector]; } -- (void)disableReloadCommand +- (void)reloadFromKeyCommand { - if ([self isReloadCommandRegistered]) { - [self unregisterReloadCommand]; - } + RCTTriggerReloadCommandListeners(@"Dev menu key command"); } - (std::shared_ptr)getTurboModule: diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index d065b1b7943d0a..32c627ffbb5124 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -358,16 +358,6 @@ - (void)_start jsInvoker:jsCallInvoker devMenuConfigurationDecorator:_devMenuConfigurationDecorator]; -#if RCT_DEV - /** - * Instantiating DevMenu has the side-effect of registering - * shortcuts for CMD + d, CMD + i, and CMD + n via RCTDevMenu. - * Therefore, when TurboModules are enabled, we must manually create this - * NativeModule. - */ - [_turboModuleManager moduleForName:"RCTDevMenu"]; -#endif // end RCT_DEV - // Initialize RCTModuleRegistry so that TurboModules can require other TurboModules. [_bridgeModuleDecorator.moduleRegistry setTurboModuleRegistry:_turboModuleManager];