diff --git a/change/@react-native-windows-codegen-e76cb014-87f2-4e71-af2d-1a489c5c60ca.json b/change/@react-native-windows-codegen-e76cb014-87f2-4e71-af2d-1a489c5c60ca.json new file mode 100644 index 00000000000..c187b305d1f --- /dev/null +++ b/change/@react-native-windows-codegen-e76cb014-87f2-4e71-af2d-1a489c5c60ca.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Generate fields to notify native if a JS event is being listened to", + "packageName": "@react-native-windows/codegen", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-45402c5d-6f84-4c62-930f-f77c7a9987cc.json b/change/react-native-windows-45402c5d-6f84-4c62-930f-f77c7a9987cc.json new file mode 100644 index 00000000000..437ce001ffb --- /dev/null +++ b/change/react-native-windows-45402c5d-6f84-4c62-930f-f77c7a9987cc.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Generate fields to notify native if a JS event is being listened to", + "packageName": "react-native-windows", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/codegen/src/generators/GenerateComponentWindows.ts b/packages/@react-native-windows/codegen/src/generators/GenerateComponentWindows.ts index 36c2cd2e1e1..189c0b940d3 100644 --- a/packages/@react-native-windows/codegen/src/generators/GenerateComponentWindows.ts +++ b/packages/@react-native-windows/codegen/src/generators/GenerateComponentWindows.ts @@ -342,6 +342,15 @@ export function createComponentGenerator({ }) .join('\n'); + const eventPropFields = componentShape.events.length + ? ` // These fields can be used to determine if JS has registered for this event\n` + + componentShape.events + .map(event => { + return ` REACT_FIELD(${event.name})\n bool ${event.name}{false};\n`; + }) + .join('\n') + : ''; + const propInitializers = componentShape.props .map(prop => { if (prop.typeAnnotation.type === 'MixedTypeAnnotation') @@ -350,6 +359,14 @@ export function createComponentGenerator({ }) .join('\n'); + const eventPropInitializers = componentShape.events.length + ? componentShape.events + .map(event => { + return ` ${event.name} = cloneFromProps->${event.name};`; + }) + .join('\n') + : ''; + const propObjectTypes = propObjectAliases.jobs .map(propObjectTypeName => { const propObjectType = propObjectAliases.types[propObjectTypeName]!; @@ -578,8 +595,14 @@ ${ .replace(/::_EVENT_EMITTER_NAME_::/g, eventEmitterName) .replace(/::_PROPS_NAME_::/g, propsName) .replace(/::_COMPONENT_NAME_::/g, componentName) - .replace(/::_PROP_INITIALIZERS_::/g, propInitializers) - .replace(/::_PROPS_FIELDS_::/g, propsFields) + .replace( + /::_PROP_INITIALIZERS_::/g, + [propInitializers, eventPropInitializers].join('\n'), + ) + .replace( + /::_PROPS_FIELDS_::/g, + [propsFields, eventPropFields].join('\n'), + ) .replace(/::_NAMESPACE_::/g, namespace) .replace(/\n\n\n+/g, '\n\n'); }; diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/MovingLight.cpp b/packages/sample-custom-component/windows/SampleCustomComponent/MovingLight.cpp index ded0349f5fd..e54b7bd38d6 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/MovingLight.cpp +++ b/packages/sample-custom-component/windows/SampleCustomComponent/MovingLight.cpp @@ -29,6 +29,25 @@ struct MovingLight : public winrt::implements, m_eventParam = newProps->eventParam; } + if (!oldProps || oldProps->onSomething != newProps->onSomething) { + if (newProps->onSomething) { + m_pointerPressedRevoker = view.PointerPressed( + winrt::auto_revoke, + [wkThis = get_weak()]( + const winrt::IInspectable & /*sender*/, + const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs & /*args*/) { + if (auto strongThis = wkThis.get()) { + if (auto eventEmitter = strongThis->EventEmitter()) { + eventEmitter->onSomething( + {.value = strongThis->m_eventParam ? *strongThis->m_eventParam : "No eventParam set"}); + } + } + }); + } else { + m_pointerPressedRevoker.revoke(); + } + } + Codegen::BaseMovingLight::UpdateProps(sender, newProps, oldProps); } @@ -40,17 +59,6 @@ struct MovingLight : public winrt::implements, void Initialize(const winrt::Microsoft::ReactNative::ComponentView &sender) noexcept override { auto view = sender.as(); - view.PointerPressed( - [wkThis = get_weak()]( - const winrt::IInspectable & /*sender*/, - const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs & /*args*/) { - if (auto strongThis = wkThis.get()) { - if (auto eventEmitter = strongThis->EventEmitter()) { - eventEmitter->onSomething( - {.value = strongThis->m_eventParam ? *strongThis->m_eventParam : "No eventParam set"}); - } - } - }); } winrt::Microsoft::UI::Composition::Visual CreateVisual( @@ -92,6 +100,7 @@ struct MovingLight : public winrt::implements, std::optional m_eventParam; winrt::Microsoft::UI::Composition::SpriteVisual m_visual{nullptr}; winrt::Microsoft::UI::Composition::SpotLight m_spotlight{nullptr}; + winrt::Microsoft::ReactNative::Composition::ViewComponentView::PointerPressed_revoker m_pointerPressedRevoker; }; void RegisterMovingLightNativeComponent( diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CalendarView.g.h b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CalendarView.g.h index edd1d5c8f1e..97936e547c7 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CalendarView.g.h +++ b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CalendarView.g.h @@ -25,7 +25,8 @@ struct CalendarViewProps : winrt::implements(); - label = cloneFromProps->label; + label = cloneFromProps->label; + onSelectedDatesChanged = cloneFromProps->onSelectedDatesChanged; } } @@ -36,6 +37,10 @@ struct CalendarViewProps : winrt::implements(); selectedIndex = cloneFromProps->selectedIndex; - placeholder = cloneFromProps->placeholder; + placeholder = cloneFromProps->placeholder; + onSelectionChanged = cloneFromProps->onSelectionChanged; } } @@ -40,6 +41,10 @@ struct ComboBoxProps : winrt::implements placeholder; + // These fields can be used to determine if JS has registered for this event + REACT_FIELD(onSelectionChanged) + bool onSelectionChanged{false}; + const winrt::Microsoft::ReactNative::ViewProps ViewProps; }; diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CustomAccessibility.g.h b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CustomAccessibility.g.h index d372f4e7d32..78b3be8900e 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CustomAccessibility.g.h +++ b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/CustomAccessibility.g.h @@ -25,6 +25,7 @@ struct CustomAccessibilityProps : winrt::implements(); + } } diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/DrawingIsland.g.h b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/DrawingIsland.g.h index acb9244cef6..e0d24476147 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/DrawingIsland.g.h +++ b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/DrawingIsland.g.h @@ -25,6 +25,7 @@ struct DrawingIslandProps : winrt::implements(); + } } diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/MovingLight.g.h b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/MovingLight.g.h index b14125acc4c..7ad9144fd12 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/MovingLight.g.h +++ b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/MovingLight.g.h @@ -38,7 +38,11 @@ struct MovingLightProps : winrt::implementscolor; testMixed = cloneFromProps->testMixed.Copy(); eventParam = cloneFromProps->eventParam; - objectProp = cloneFromProps->objectProp; + objectProp = cloneFromProps->objectProp; + onSomething = cloneFromProps->onSomething; + onTestObjectEvent = cloneFromProps->onTestObjectEvent; + onEventWithInlineTypes = cloneFromProps->onEventWithInlineTypes; + onEventWithMultipleAliasTypes = cloneFromProps->onEventWithMultipleAliasTypes; } } @@ -61,6 +65,19 @@ struct MovingLightProps : winrt::implements objectProp; + // These fields can be used to determine if JS has registered for this event + REACT_FIELD(onSomething) + bool onSomething{false}; + + REACT_FIELD(onTestObjectEvent) + bool onTestObjectEvent{false}; + + REACT_FIELD(onEventWithInlineTypes) + bool onEventWithInlineTypes{false}; + + REACT_FIELD(onEventWithMultipleAliasTypes) + bool onEventWithMultipleAliasTypes{false}; + const winrt::Microsoft::ReactNative::ViewProps ViewProps; }; diff --git a/vnext/overrides.json b/vnext/overrides.json index bfe6a2804b9..a8de6e0f3ca 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -585,6 +585,12 @@ "baseFile": "packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js", "baseHash": "a54d194cd731380ec0f2ff86b8b1f5ff072ec0ba" }, + { + "type": "derived", + "file": "src-win/Libraries/NativeComponent/ViewConfigIgnore.windows.js", + "baseFile": "packages/react-native/Libraries/NativeComponent/ViewConfigIgnore.js", + "baseHash": "ad955a66606d12afc8402e82afdd8103c5ca8b03" + }, { "type": "derived", "file": "src-win/Libraries/NativeModules/specs/NativeDialogManagerWindows.js", diff --git a/vnext/src-win/Libraries/NativeComponent/ViewConfigIgnore.windows.js b/vnext/src-win/Libraries/NativeComponent/ViewConfigIgnore.windows.js new file mode 100644 index 00000000000..f14c0a218fb --- /dev/null +++ b/vnext/src-win/Libraries/NativeComponent/ViewConfigIgnore.windows.js @@ -0,0 +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. + * + * @flow strict + */ + +const ignoredViewConfigProps = new WeakSet < { ...} > (); + +/** + * Decorates ViewConfig values that are dynamically injected by the library, + * react-native-gesture-handler. (T45765076) + */ +export function DynamicallyInjectedByGestureHandler (object: T): T { + ignoredViewConfigProps.add(object); + return object; +} + +/** + * On iOS, ViewManager event declarations generate {eventName}: true entries + * in ViewConfig valueAttributes. These entries aren't generated for Android. + * This annotation allows Static ViewConfigs to insert these entries into + * iOS but not Android. + * + * In the future, we want to remove this platform-inconsistency. We want + * to set RN$ViewConfigEventValidAttributesDisabled = true server-side, + * so that iOS does not generate validAttributes from event props in iOS RCTViewManager, + * since Android does not generate validAttributes from events props in Android ViewManager. + * + * TODO(T110872225): Remove this logic, after achieving platform-consistency + */ +export function ConditionallyIgnoredEventHandlers< + const T: { +[name: string]: true }, +> (value: T): T | void { + return value; +} + +export function isIgnored(value: mixed): boolean { + if (typeof value === 'object' && value != null) { + return ignoredViewConfigProps.has(value); + } + return false; +}