diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index 1cbd0c977ab..387126e40ed 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -1706,6 +1706,65 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = { }, ], }, + MenuItem: { + type: 'ObjectTypeAnnotation', + properties: [ + { + name: 'label', + optional: false, + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + name: 'onPress', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'VoidTypeAnnotation', + }, + params: [ + { + name: 'value', + optional: false, + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + { + name: 'flag', + optional: false, + typeAnnotation: { + type: 'BooleanTypeAnnotation', + }, + }, + ], + }, + }, + { + name: 'shortcut', + optional: true, + typeAnnotation: { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'StringTypeAnnotation', + }, + }, + }, + { + name: 'items', + optional: true, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'TypeAliasTypeAnnotation', + name: 'MenuItem', + }, + }, + }, + ], + }, }, enumMap: { EnumInt: { @@ -2236,6 +2295,26 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = { params: [], }, }, + { + name: 'setMenu', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'VoidTypeAnnotation', + }, + params: [ + { + name: 'menuItem', + optional: false, + typeAnnotation: { + type: 'TypeAliasTypeAnnotation', + name: 'MenuItem', + }, + }, + ], + }, + }, { name: 'emitCustomDeviceEvent', optional: false, diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap index c131ee0bf6c..cef33f44c2e 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap @@ -253,6 +253,13 @@ static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_voidFunc(jsi: ); return jsi::Value::undefined(); } +static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_setMenu(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + static_cast(&turboModule)->setMenu( + rt, + args[0].asObject(rt) + ); + return jsi::Value::undefined(); +} static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_emitCustomDeviceEvent(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { static_cast(&turboModule)->emitCustomDeviceEvent( rt, @@ -308,6 +315,7 @@ NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared methodMap_[\\"getValueWithPromise\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getValueWithPromise}; methodMap_[\\"getWithWithOptionalArgs\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getWithWithOptionalArgs}; methodMap_[\\"voidFunc\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_voidFunc}; + methodMap_[\\"setMenu\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_setMenu}; methodMap_[\\"emitCustomDeviceEvent\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_emitCustomDeviceEvent}; methodMap_[\\"voidFuncThrows\\"] = MethodMetadata {0, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_voidFuncThrows}; methodMap_[\\"getObjectThrows\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getObjectThrows}; diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index e990265f483..60be24c0acf 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -582,6 +582,70 @@ struct [[deprecated(\\"Use SampleTurboModuleCxxValueStructBridging instead.\\")] }; + +#pragma mark - SampleTurboModuleCxxBaseMenuItem + +template +struct [[deprecated(\\"Use SampleTurboModuleCxxMenuItem instead.\\")]] SampleTurboModuleCxxBaseMenuItem { + P0 label; + P1 onPress; + P2 shortcut; + P3 items; + bool operator==(const SampleTurboModuleCxxBaseMenuItem &other) const { + return label == other.label && onPress == other.onPress && shortcut == other.shortcut && items == other.items; + } +}; + +template +struct [[deprecated(\\"Use SampleTurboModuleCxxMenuItemBridging instead.\\")]] SampleTurboModuleCxxBaseMenuItemBridging { + static SampleTurboModuleCxxBaseMenuItem fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + SampleTurboModuleCxxBaseMenuItem result{ + bridging::fromJs(rt, value.getProperty(rt, \\"label\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"onPress\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"shortcut\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"items\\"), jsInvoker)}; + return result; + } + +#ifdef DEBUG + static jsi::String labelToJs(jsi::Runtime &rt, P0 value) { + return bridging::toJs(rt, value); + } + + static jsi::Function onPressToJs(jsi::Runtime &rt, P1 value) { + return bridging::toJs(rt, value); + } + + static std::optional shortcutToJs(jsi::Runtime &rt, P2 value) { + return bridging::toJs(rt, value); + } + + static jsi::Array itemsToJs(jsi::Runtime &rt, P3 value) { + return bridging::toJs(rt, value); + } +#endif + + static jsi::Object toJs( + jsi::Runtime &rt, + const SampleTurboModuleCxxBaseMenuItem &value, + const std::shared_ptr &jsInvoker) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"label\\", bridging::toJs(rt, value.label, jsInvoker)); + result.setProperty(rt, \\"onPress\\", bridging::toJs(rt, value.onPress, jsInvoker)); + if (value.shortcut) { + result.setProperty(rt, \\"shortcut\\", bridging::toJs(rt, value.shortcut.value(), jsInvoker)); + } + if (value.items) { + result.setProperty(rt, \\"items\\", bridging::toJs(rt, value.items.value(), jsInvoker)); + } + return result; + } +}; + + #pragma mark - SampleTurboModuleCxxConstantsStruct template @@ -857,6 +921,72 @@ struct SampleTurboModuleCxxValueStructBridging { } }; + + +#pragma mark - SampleTurboModuleCxxMenuItem + +template +struct SampleTurboModuleCxxMenuItem { + P0 label; + P1 onPress; + P2 shortcut; + std::optional>> items; + bool operator==(const SampleTurboModuleCxxMenuItem &other) const { + return label == other.label && onPress == other.onPress && shortcut == other.shortcut && items == other.items; + } +}; + +template +struct SampleTurboModuleCxxMenuItemBridging { + static T types; + + static T fromJs( + jsi::Runtime &rt, + const jsi::Object &value, + const std::shared_ptr &jsInvoker) { + T result{ + bridging::fromJs(rt, value.getProperty(rt, \\"label\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"onPress\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"shortcut\\"), jsInvoker), + bridging::fromJs(rt, value.getProperty(rt, \\"items\\"), jsInvoker)}; + return result; + } + +#ifdef DEBUG + static jsi::String labelToJs(jsi::Runtime &rt, decltype(types.label) value) { + return bridging::toJs(rt, value); + } + + static jsi::Function onPressToJs(jsi::Runtime &rt, decltype(types.onPress) value) { + return bridging::toJs(rt, value); + } + + static std::optional shortcutToJs(jsi::Runtime &rt, decltype(types.shortcut) value) { + return bridging::toJs(rt, value); + } + + static jsi::Array itemsToJs(jsi::Runtime &rt, decltype(types.items) value) { + return bridging::toJs(rt, value); + } +#endif + + static jsi::Object toJs( + jsi::Runtime &rt, + const T &value, + const std::shared_ptr &jsInvoker) { + auto result = facebook::jsi::Object(rt); + result.setProperty(rt, \\"label\\", bridging::toJs(rt, value.label, jsInvoker)); + result.setProperty(rt, \\"onPress\\", bridging::toJs(rt, value.onPress, jsInvoker)); + if (value.shortcut) { + result.setProperty(rt, \\"shortcut\\", bridging::toJs(rt, value.shortcut.value(), jsInvoker)); + } + if (value.items) { + result.setProperty(rt, \\"items\\", bridging::toJs(rt, value.items.value(), jsInvoker)); + } + return result; + } +}; + class JSI_EXPORT NativeSampleTurboModuleCxxSpecJSI : public TurboModule { protected: NativeSampleTurboModuleCxxSpecJSI(std::shared_ptr jsInvoker); @@ -883,6 +1013,7 @@ public: virtual jsi::Value getValueWithPromise(jsi::Runtime &rt, bool error) = 0; virtual std::optional getWithWithOptionalArgs(jsi::Runtime &rt, std::optional optionalArg) = 0; virtual void voidFunc(jsi::Runtime &rt) = 0; + virtual void setMenu(jsi::Runtime &rt, jsi::Object menuItem) = 0; virtual void emitCustomDeviceEvent(jsi::Runtime &rt, jsi::String eventName) = 0; virtual void voidFuncThrows(jsi::Runtime &rt) = 0; virtual jsi::Object getObjectThrows(jsi::Runtime &rt, jsi::Object arg) = 0; @@ -1079,6 +1210,14 @@ private: return bridging::callFromJs( rt, &T::voidFunc, jsInvoker_, instance_); } + void setMenu(jsi::Runtime &rt, jsi::Object menuItem) override { + static_assert( + bridging::getParameterCount(&T::setMenu) == 2, + \\"Expected setMenu(...) to have 2 parameters\\"); + + return bridging::callFromJs( + rt, &T::setMenu, jsInvoker_, instance_, std::move(menuItem)); + } void emitCustomDeviceEvent(jsi::Runtime &rt, jsi::String eventName) override { static_assert( bridging::getParameterCount(&T::emitCustomDeviceEvent) == 2, diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 82c78afb875..c13b90340d8 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -235,7 +235,7 @@ function parseObjectProperty( ); if ( - propertyTypeAnnotation.type === 'FunctionTypeAnnotation' || + (propertyTypeAnnotation.type === 'FunctionTypeAnnotation' && !cxxOnly) || propertyTypeAnnotation.type === 'PromiseTypeAnnotation' || propertyTypeAnnotation.type === 'VoidTypeAnnotation' ) { diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp index 2fbebf8eae0..abec005d02e 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp @@ -169,6 +169,15 @@ void NativeCxxModuleExample::voidFunc(jsi::Runtime& rt) { // Nothing to do } +void NativeCxxModuleExample::setMenu(jsi::Runtime& rt, MenuItem menuItem) { + menuItem.onPress("value", true); + if (menuItem.items) { + for (auto subMenuItem : *menuItem.items) { + subMenuItem.onPress("another value", false); + } + } +} + void NativeCxxModuleExample::emitCustomDeviceEvent( jsi::Runtime& rt, jsi::String eventName) { diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h index 3e2e92692f8..aaec3839fcb 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h @@ -104,6 +104,17 @@ template <> struct Bridging : NativeCxxModuleExampleCxxGraphNodeBridging {}; +#pragma mark - functional object properties + +using MenuItem = NativeCxxModuleExampleCxxMenuItem< + std::string, + AsyncCallback, + std::optional>; + +template <> +struct Bridging + : NativeCxxModuleExampleCxxMenuItemBridging {}; + #pragma mark - implementation class NativeCxxModuleExample : public NativeCxxModuleExampleCxxSpec { @@ -171,6 +182,8 @@ class NativeCxxModuleExample void voidFunc(jsi::Runtime& rt); + void setMenu(jsi::Runtime& rt, MenuItem menuItem); + void emitCustomDeviceEvent(jsi::Runtime& rt, jsi::String eventName); void voidFuncThrows(jsi::Runtime& rt); diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js index 74e628a1b10..ae4e4658d5c 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js @@ -67,6 +67,13 @@ export type GraphNode = { neighbors?: Array, }; +export type MenuItem = { + label: string, + onPress: (value: string, flag: boolean) => void, + shortcut?: ?string, + items?: Array, +}; + export interface Spec extends TurboModule { +getArray: (arg: Array) => Array; +getBool: (arg: boolean) => boolean; @@ -92,6 +99,7 @@ export interface Spec extends TurboModule { +getValueWithPromise: (error: boolean) => Promise; +getWithWithOptionalArgs: (optionalArg?: boolean) => ?boolean; +voidFunc: () => void; + +setMenu: (menuItem: MenuItem) => void; +emitCustomDeviceEvent: (eventName: string) => void; +voidFuncThrows: () => void; +getObjectThrows: (arg: ObjectStruct) => ObjectStruct; diff --git a/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js index 2564e0e07f0..bfdb6222621 100644 --- a/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js @@ -57,6 +57,7 @@ type Examples = | 'promise' | 'rejectPromise' | 'voidFunc' + | 'setMenuItem' | 'optionalArgs' | 'emitDeviceEvent'; @@ -135,6 +136,28 @@ class NativeCxxModuleExampleExample extends React.Component<{||}, State> { .then(() => {}) .catch(e => this._setResult('rejectPromise', e.message)), voidFunc: () => NativeCxxModuleExample?.voidFunc(), + setMenuItem: () => { + let curValue = ''; + NativeCxxModuleExample?.setMenu({ + label: 'File', + onPress: (value: string, flag: boolean) => { + curValue = `${value}: ${flag.toString()}`; + this._setResult('setMenuItem', curValue); + }, + items: [ + { + label: 'Open', + onPress: (value: string, flag: boolean) => { + this._setResult( + 'setMenuItem', + `${curValue} - ${value}: ${flag.toString()}`, + ); + }, + }, + ], + shortcut: 'ctrl+shift+f', + }); + }, optionalArgs: () => NativeCxxModuleExample?.getWithWithOptionalArgs(), emitDeviceEvent: () => { const CUSTOM_EVENT_TYPE = 'myCustomDeviceEvent';