diff --git a/change/@react-native-windows-codegen-8d6e2cd0-f94e-4aea-93b7-ac7b5edb0529.json b/change/@react-native-windows-codegen-8d6e2cd0-f94e-4aea-93b7-ac7b5edb0529.json new file mode 100644 index 00000000000..33be0ca41a2 --- /dev/null +++ b/change/@react-native-windows-codegen-8d6e2cd0-f94e-4aea-93b7-ac7b5edb0529.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[Fabric] Add FocusNavigationDirection and allow overriding of default command handling", + "packageName": "@react-native-windows/codegen", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-53895955-fc85-4bdd-bf07-423af7773f83.json b/change/react-native-windows-53895955-fc85-4bdd-bf07-423af7773f83.json new file mode 100644 index 00000000000..eb7dbb612be --- /dev/null +++ b/change/react-native-windows-53895955-fc85-4bdd-bf07-423af7773f83.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[Fabric] Add FocusNavigationDirection and allow overriding of default command handling", + "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 8adf152ae60..c0868bc6eb4 100644 --- a/packages/@react-native-windows/codegen/src/generators/GenerateComponentWindows.ts +++ b/packages/@react-native-windows/codegen/src/generators/GenerateComponentWindows.ts @@ -334,9 +334,9 @@ export function createComponentGenerator({ }).join('\n\n') : ''; - const commandHandler = hasAnyCommands ? `void HandleCommand(const winrt::Microsoft::ReactNative::ComponentView &view, winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { - args; + const commandHandler = hasAnyCommands ? `void HandleCommand(const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept { auto userData = view.UserData().as(); + auto commandName = args.CommandName(); ${componentShape.commands.map(command => { const commaSeparatedCommandArgs = command.typeAnnotation.params.map(param => param.name).join(', '); return ` if (commandName == L"${command.name}") { @@ -344,7 +344,7 @@ ${command.typeAnnotation.params.length !== 0 ? ` ${command.typeAnnotation.p const commandArgType = translateCommandParamType(param.typeAnnotation, commandAliases, `${componentName}_${command.name}`, cppCodegenOptions); return `${(param.optional && !commandArgType.alreadySupportsOptionalOrHasDefault) ? `std::optional<${commandArgType.type}>` : commandArgType.type} ${param.name};`; }).join('\n')} - winrt::Microsoft::ReactNative::ReadArgs(args, ${commaSeparatedCommandArgs});` : ''} + winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), ${commaSeparatedCommandArgs});` : ''} userData->Handle${capitalizeFirstLetter(command.name)}Command(${commaSeparatedCommandArgs}); return; }` @@ -352,10 +352,9 @@ ${command.typeAnnotation.params.length !== 0 ? ` ${command.typeAnnotation.p }` : ''; const registerCommandHandler = hasAnyCommands ? ` builder.SetCustomCommandHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { + const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept { auto userData = view.UserData().as(); - userData->HandleCommand(view, commandName, args); + userData->HandleCommand(view, args); });` : ''; const baseType = baseStructTemplate diff --git a/packages/sample-custom-component/codegen/react/components/SampleCustomComponent/MovingLight.g.h b/packages/sample-custom-component/codegen/react/components/SampleCustomComponent/MovingLight.g.h index 7cf242f0967..35859463a4f 100644 --- a/packages/sample-custom-component/codegen/react/components/SampleCustomComponent/MovingLight.g.h +++ b/packages/sample-custom-component/codegen/react/components/SampleCustomComponent/MovingLight.g.h @@ -115,12 +115,12 @@ struct BaseMovingLight { // You must provide an implementation of this method to handle the "setLightOn" command virtual void HandleSetLightOnCommand(bool value) noexcept = 0; - void HandleCommand(const winrt::Microsoft::ReactNative::ComponentView &view, winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { - args; + void HandleCommand(const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept { auto userData = view.UserData().as(); + auto commandName = args.CommandName(); if (commandName == L"setLightOn") { bool value; - winrt::Microsoft::ReactNative::ReadArgs(args, value); + winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), value); userData->HandleSetLightOnCommand(value); return; } @@ -175,10 +175,9 @@ void RegisterMovingLightNativeComponent( } builder.SetCustomCommandHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { + const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept { auto userData = view.UserData().as(); - userData->HandleCommand(view, commandName, args); + userData->HandleCommand(view, args); }); if constexpr (&TUserData::MountChildComponentView != &BaseMovingLight::MountChildComponentView) { diff --git a/vnext/Microsoft.ReactNative/ComponentView.idl b/vnext/Microsoft.ReactNative/ComponentView.idl index 1c00eb676f3..2481b3344db 100644 --- a/vnext/Microsoft.ReactNative/ComponentView.idl +++ b/vnext/Microsoft.ReactNative/ComponentView.idl @@ -34,6 +34,15 @@ namespace Microsoft.ReactNative All = 0x0000000F, }; + enum FocusNavigationDirection + { + None, + Next, + Previous, + First, + Last, + }; + [webhosthidden] [experimental] interface IComponentState @@ -46,6 +55,7 @@ namespace Microsoft.ReactNative [experimental] [webhosthidden] runtimeclass LosingFocusEventArgs : Microsoft.ReactNative.Composition.Input.RoutedEventArgs { + FocusNavigationDirection Direction { get; }; Microsoft.ReactNative.ComponentView NewFocusedComponent { get; }; Microsoft.ReactNative.ComponentView OldFocusedComponent { get; }; @@ -56,6 +66,7 @@ namespace Microsoft.ReactNative [experimental] [webhosthidden] runtimeclass GettingFocusEventArgs : Microsoft.ReactNative.Composition.Input.RoutedEventArgs { + FocusNavigationDirection Direction { get; }; Microsoft.ReactNative.ComponentView NewFocusedComponent { get; }; Microsoft.ReactNative.ComponentView OldFocusedComponent { get; }; diff --git a/vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp index a8a224b9784..59f310e84b6 100644 --- a/vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp @@ -256,11 +256,9 @@ void ComponentView::CustomCommandHandler(const HandleCommandDelegate &handler) n m_customCommandHandler = handler; } -void ComponentView::HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { +void ComponentView::HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { if (m_customCommandHandler) { - m_customCommandHandler(*this, commandName, args); + m_customCommandHandler(*this, args); } } @@ -285,7 +283,7 @@ void ComponentView::parent(const winrt::Microsoft::ReactNative::ComponentView &p m_parent = parent; if (!parent) { if (oldRootView && oldRootView->GetFocusedComponent() == *this) { - oldRootView->TrySetFocusedComponent(oldParent); + oldRootView->TrySetFocusedComponent(oldParent, winrt::Microsoft::ReactNative::FocusNavigationDirection::None); } } if (parent) { @@ -424,7 +422,7 @@ void ComponentView::GotFocus(winrt::event_token const &token) noexcept { bool ComponentView::TryFocus() noexcept { if (auto root = rootComponentView()) { - return root->TrySetFocusedComponent(*get_strong()); + return root->TrySetFocusedComponent(*get_strong(), winrt::Microsoft::ReactNative::FocusNavigationDirection::None); } return false; diff --git a/vnext/Microsoft.ReactNative/Fabric/ComponentView.h b/vnext/Microsoft.ReactNative/Fabric/ComponentView.h index a76f95c9a1e..b0bae54934f 100644 --- a/vnext/Microsoft.ReactNative/Fabric/ComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/ComponentView.h @@ -228,9 +228,7 @@ struct ComponentView : public ComponentViewT { virtual void UnmountChildComponentView( const winrt::Microsoft::ReactNative::ComponentView &childComponentView, uint32_t index) noexcept; - virtual void HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept; + virtual void HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept; virtual void FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) noexcept; virtual void OnPointerEntered( const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index bad5b024bb4..baf6a03ee7c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -248,23 +248,26 @@ void ComponentView::updateEventEmitter(facebook::react::EventEmitter::Shared con base_type::updateEventEmitter(eventEmitter); } -void ComponentView::HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { +void ComponentView::HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { + base_type::HandleCommand(args); + if (args.Handled()) + return; + + auto commandName = args.CommandName(); if (commandName == L"focus") { if (auto root = rootComponentView()) { - root->TrySetFocusedComponent(*get_strong()); + root->TrySetFocusedComponent(*get_strong(), winrt::Microsoft::ReactNative::FocusNavigationDirection::None); } return; } if (commandName == L"blur") { if (auto root = rootComponentView()) { - root->TrySetFocusedComponent(nullptr); // Todo store this component as previously focused element + root->TrySetFocusedComponent( + nullptr, winrt::Microsoft::ReactNative::FocusNavigationDirection::None); // Todo store this component as + // previously focused element } return; } - - base_type::HandleCommand(commandName, args); } bool ComponentView::CapturePointer(const winrt::Microsoft::ReactNative::Composition::Input::Pointer &pointer) noexcept { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h index b2913ce11af..d2482ec8475 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h @@ -38,8 +38,7 @@ struct ComponentView : public ComponentViewT< virtual winrt::Microsoft::ReactNative::Composition::Experimental::IVisual OuterVisual() const noexcept; void updateEventEmitter(facebook::react::EventEmitter::Shared const &eventEmitter) noexcept override; const facebook::react::SharedViewEventEmitter &GetEventEmitter() const noexcept; - void HandleCommand(winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept - override; + void HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept override; facebook::react::Props::Shared props() noexcept override; virtual const facebook::react::SharedViewProps &viewProps() const noexcept { static facebook::react::SharedViewProps emptyProps; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp index 604d05f448a..f49951ecd31 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp @@ -67,17 +67,21 @@ struct TraceUpdate { }; void DebuggingOverlayComponentView::HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { + const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { + base_type::HandleCommand(args); + if (args.Handled()) + return; + + auto commandName = args.CommandName(); if (commandName == L"highlightTraceUpdates") { std::vector updates; - winrt::Microsoft::ReactNative::ReadArgs(args, updates); + winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), updates); // TODO should create visuals that get removed after 2 seconds return; } if (commandName == L"highlightElements") { std::vector elements; - winrt::Microsoft::ReactNative::ReadArgs(args, elements); + winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), elements); if (auto root = rootComponentView()) { auto rootVisual = root->OuterVisual(); @@ -106,8 +110,6 @@ void DebuggingOverlayComponentView::HandleCommand( } return; } - - base_type::HandleCommand(commandName, args); } } // namespace winrt::Microsoft::ReactNative::Composition::implementation diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.h index 8ee60bf1049..567b5db1451 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.h @@ -29,8 +29,7 @@ struct DebuggingOverlayComponentView facebook::react::Tag tag, winrt::Microsoft::ReactNative::ReactContext const &reactContext); - void HandleCommand(winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept - override; + void HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept override; private: uint32_t m_activeOverlays{0}; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.cpp index cbf444207ce..ff5a0dd8404 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.cpp @@ -22,19 +22,26 @@ int32_t GotFocusEventArgs::OriginalSource() noexcept { LosingFocusEventArgs::LosingFocusEventArgs( const winrt::Microsoft::ReactNative::ComponentView &originalSource, + winrt::Microsoft::ReactNative::FocusNavigationDirection direction, const winrt::Microsoft::ReactNative::ComponentView &oldFocusedComponent, const winrt::Microsoft::ReactNative::ComponentView &newFocusedComponent) : m_originalSource(originalSource ? originalSource.Tag() : -1), + m_direction(direction), m_old(oldFocusedComponent), m_new(newFocusedComponent) {} -int32_t LosingFocusEventArgs::OriginalSource() noexcept { +int32_t LosingFocusEventArgs::OriginalSource() const noexcept { return m_originalSource; } -winrt::Microsoft::ReactNative::ComponentView LosingFocusEventArgs::NewFocusedComponent() noexcept { + +winrt::Microsoft::ReactNative::FocusNavigationDirection LosingFocusEventArgs::Direction() const noexcept { + return m_direction; +} + +winrt::Microsoft::ReactNative::ComponentView LosingFocusEventArgs::NewFocusedComponent() const noexcept { return m_new; } -winrt::Microsoft::ReactNative::ComponentView LosingFocusEventArgs::OldFocusedComponent() noexcept { +winrt::Microsoft::ReactNative::ComponentView LosingFocusEventArgs::OldFocusedComponent() const noexcept { return m_old; } @@ -58,19 +65,26 @@ void LosingFocusEventArgs::TrySetNewFocusedComponent( GettingFocusEventArgs::GettingFocusEventArgs( const winrt::Microsoft::ReactNative::ComponentView &originalSource, + winrt::Microsoft::ReactNative::FocusNavigationDirection direction, const winrt::Microsoft::ReactNative::ComponentView &oldFocusedComponent, const winrt::Microsoft::ReactNative::ComponentView &newFocusedComponent) : m_originalSource(originalSource ? originalSource.Tag() : -1), + m_direction(direction), m_old(oldFocusedComponent), m_new(newFocusedComponent) {} -int32_t GettingFocusEventArgs::OriginalSource() noexcept { +int32_t GettingFocusEventArgs::OriginalSource() const noexcept { return m_originalSource; } -winrt::Microsoft::ReactNative::ComponentView GettingFocusEventArgs::NewFocusedComponent() noexcept { + +winrt::Microsoft::ReactNative::FocusNavigationDirection GettingFocusEventArgs::Direction() const noexcept { + return m_direction; +} + +winrt::Microsoft::ReactNative::ComponentView GettingFocusEventArgs::NewFocusedComponent() const noexcept { return m_new; } -winrt::Microsoft::ReactNative::ComponentView GettingFocusEventArgs::OldFocusedComponent() noexcept { +winrt::Microsoft::ReactNative::ComponentView GettingFocusEventArgs::OldFocusedComponent() const noexcept { return m_old; } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.h b/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.h index 3789c1f5370..d09db5f5db8 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/FocusManager.h @@ -32,17 +32,21 @@ struct LosingFocusEventArgs : winrt::Microsoft::ReactNative::implementation::LosingFocusEventArgsT { LosingFocusEventArgs( const winrt::Microsoft::ReactNative::ComponentView &originalSource, + winrt::Microsoft::ReactNative::FocusNavigationDirection direction, const winrt::Microsoft::ReactNative::ComponentView &oldFocusedComponent, const winrt::Microsoft::ReactNative::ComponentView &newFocusedComponent); - int32_t OriginalSource() noexcept; - winrt::Microsoft::ReactNative::ComponentView NewFocusedComponent() noexcept; - winrt::Microsoft::ReactNative::ComponentView OldFocusedComponent() noexcept; + int32_t OriginalSource() const noexcept; + winrt::Microsoft::ReactNative::FocusNavigationDirection Direction() const noexcept; + + winrt::Microsoft::ReactNative::ComponentView NewFocusedComponent() const noexcept; + winrt::Microsoft::ReactNative::ComponentView OldFocusedComponent() const noexcept; void TryCancel() noexcept; void TrySetNewFocusedComponent(const winrt::Microsoft::ReactNative::ComponentView &newFocusedComponent) noexcept; private: const int32_t m_originalSource; + const winrt::Microsoft::ReactNative::FocusNavigationDirection m_direction; winrt::Microsoft::ReactNative::ComponentView m_old{nullptr}; winrt::Microsoft::ReactNative::ComponentView m_new{nullptr}; }; @@ -51,17 +55,20 @@ struct GettingFocusEventArgs : winrt::Microsoft::ReactNative::implementation::GettingFocusEventArgsT { GettingFocusEventArgs( const winrt::Microsoft::ReactNative::ComponentView &originalSource, + winrt::Microsoft::ReactNative::FocusNavigationDirection direction, const winrt::Microsoft::ReactNative::ComponentView &oldFocusedComponent, const winrt::Microsoft::ReactNative::ComponentView &newFocusedComponent); - int32_t OriginalSource() noexcept; - winrt::Microsoft::ReactNative::ComponentView NewFocusedComponent() noexcept; - winrt::Microsoft::ReactNative::ComponentView OldFocusedComponent() noexcept; + int32_t OriginalSource() const noexcept; + winrt::Microsoft::ReactNative::FocusNavigationDirection Direction() const noexcept; + winrt::Microsoft::ReactNative::ComponentView NewFocusedComponent() const noexcept; + winrt::Microsoft::ReactNative::ComponentView OldFocusedComponent() const noexcept; void TryCancel() noexcept; void TrySetNewFocusedComponent(const winrt::Microsoft::ReactNative::ComponentView &newFocusedComponent) noexcept; private: const int32_t m_originalSource; + const winrt::Microsoft::ReactNative::FocusNavigationDirection m_direction; winrt::Microsoft::ReactNative::ComponentView m_old{nullptr}; winrt::Microsoft::ReactNative::ComponentView m_new{nullptr}; }; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp index 7abb166e3e2..90ea200458c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp @@ -191,9 +191,8 @@ void WindowsModalHostComponentView::UnmountChildComponentView( } void WindowsModalHostComponentView::HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { - Super::HandleCommand(commandName, args); + const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { + Super::HandleCommand(args); } void WindowsModalHostComponentView::updateProps( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h index 6bf56707180..547b9f3cc50 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h @@ -28,8 +28,7 @@ struct WindowsModalHostComponentView void UnmountChildComponentView( const winrt::Microsoft::ReactNative::ComponentView &childComponentView, uint32_t index) noexcept override; - void HandleCommand(winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept - override; + void HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept override; void updateState(facebook::react::State::Shared const &state, facebook::react::State::Shared const &oldState) noexcept override; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp index 8e01addc5b2..6dd450526c8 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp @@ -91,12 +91,18 @@ bool RootComponentView::NavigateFocus(const winrt::Microsoft::ReactNative::Focus ? FocusManager::FindFirstFocusableElement(*this) : FocusManager::FindLastFocusableElement(*this); if (view) { - TrySetFocusedComponent(view); + TrySetFocusedComponent( + view, + request.Reason() == winrt::Microsoft::ReactNative::FocusNavigationReason::First + ? winrt::Microsoft::ReactNative::FocusNavigationDirection::First + : winrt::Microsoft::ReactNative::FocusNavigationDirection::Last); } return view != nullptr; } -bool RootComponentView::TrySetFocusedComponent(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { +bool RootComponentView::TrySetFocusedComponent( + const winrt::Microsoft::ReactNative::ComponentView &view, + winrt::Microsoft::ReactNative::FocusNavigationDirection direction) noexcept { auto target = view; auto selfView = winrt::get_self(target); if (selfView && !selfView->focusable()) { @@ -110,7 +116,7 @@ bool RootComponentView::TrySetFocusedComponent(const winrt::Microsoft::ReactNati return false; auto losingFocusArgs = winrt::make( - target, m_focusedComponent, target); + target, direction, m_focusedComponent, target); if (m_focusedComponent) { winrt::get_self(m_focusedComponent) ->onLosingFocus(losingFocusArgs); @@ -118,7 +124,7 @@ bool RootComponentView::TrySetFocusedComponent(const winrt::Microsoft::ReactNati if (losingFocusArgs.NewFocusedComponent()) { auto gettingFocusArgs = winrt::make( - target, m_focusedComponent, losingFocusArgs.NewFocusedComponent()); + target, direction, m_focusedComponent, losingFocusArgs.NewFocusedComponent()); winrt::get_self(losingFocusArgs.NewFocusedComponent()) ->onGettingFocus(gettingFocusArgs); @@ -138,13 +144,16 @@ bool RootComponentView::TryMoveFocus(bool next) noexcept { } Mso::Functor fn = - [currentlyFocused = m_focusedComponent](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + [currentlyFocused = m_focusedComponent, next](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { if (view == currentlyFocused) return false; return winrt::get_self(view) ->rootComponentView() - ->TrySetFocusedComponent(view); + ->TrySetFocusedComponent( + view, + next ? winrt::Microsoft::ReactNative::FocusNavigationDirection::Next + : winrt::Microsoft::ReactNative::FocusNavigationDirection::Previous); }; return winrt::Microsoft::ReactNative::implementation::walkTree(m_focusedComponent, next, fn); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h index dbd08609766..7277b4a98e1 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h @@ -28,7 +28,9 @@ struct RootComponentView : RootComponentViewT(x) * m_layoutMetrics.pointScaleFactor, static_cast(y) * m_layoutMetrics.pointScaleFactor, @@ -1071,12 +1074,10 @@ void ScrollViewComponentView::HandleCommand( // No-op for now } else if (commandName == L"scrollToEnd") { bool animate; - winrt::Microsoft::ReactNative::ReadArgs(args, animate); + winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), animate); scrollToEnd(animate); } else if (commandName == L"zoomToRect") { // No-op for now - } else { - Super::HandleCommand(commandName, args); } } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h index dc294a4d9c8..5805570adff 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h @@ -74,8 +74,7 @@ struct ScrollInteractionTrackerOwner : public winrt::implements< void prepareForRecycle() noexcept override; void OnKeyDown(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override; - void HandleCommand(winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept - override; + void HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept override; facebook::react::Tag hitTest(facebook::react::Point pt, facebook::react::Point &localPt, bool ignorePointerEvents) const noexcept override; facebook::react::Point getClientOffset() const noexcept override; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp index 7924aa0c5be..129211ebf05 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp @@ -56,14 +56,14 @@ void SwitchComponentView::UnmountChildComponentView( base_type::UnmountChildComponentView(childComponentView, index); } -void SwitchComponentView::HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { +void SwitchComponentView::HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { + Super::HandleCommand(args); + if (args.Handled()) + return; + auto commandName = args.CommandName(); if (commandName == L"setValue") { // TODO - Current implementation always aligns with JS value // This will be needed when we move to using WinUI controls - } else { - Super::HandleCommand(commandName, args); } } @@ -261,7 +261,7 @@ void SwitchComponentView::OnPointerPressed( m_supressAnimationForNextFrame = true; if (auto root = rootComponentView()) { - root->TrySetFocusedComponent(*get_strong()); + root->TrySetFocusedComponent(*get_strong(), winrt::Microsoft::ReactNative::FocusNavigationDirection::None); } updateVisuals(); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h index 386af45d2c3..827409ed0c6 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.h @@ -27,8 +27,7 @@ struct SwitchComponentView : SwitchComponentViewT { winrt::Microsoft::ReactNative::ComponentView view{nullptr}; winrt::check_hresult( m_outer->QueryInterface(winrt::guid_of(), winrt::put_abi(view))); - m_outer->rootComponentView()->TrySetFocusedComponent(view); + m_outer->rootComponentView()->TrySetFocusedComponent( + view, winrt::Microsoft::ReactNative::FocusNavigationDirection::None); // assert(false); // TODO focus } @@ -522,13 +523,17 @@ WindowsTextInputComponentView::WindowsTextInputComponentView( } void WindowsTextInputComponentView::HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { + const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { + Super::HandleCommand(args); + if (args.Handled()) + return; + + auto commandName = args.CommandName(); if (commandName == L"setTextAndSelection") { int eventCount, begin, end; winrt::hstring text; - winrt::Microsoft::ReactNative::ReadArgs(args, eventCount, text, begin, end); + winrt::Microsoft::ReactNative::ReadArgs(args.CommandArgs(), eventCount, text, begin, end); if (eventCount >= m_nativeEventCount) { m_comingFromJS = true; UpdateText(winrt::to_string(text)); @@ -549,8 +554,6 @@ void WindowsTextInputComponentView::HandleCommand( m_comingFromJS = false; } - } else { - Super::HandleCommand(commandName, args); } } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h index 66ade0c1f01..e355d27624f 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h @@ -46,8 +46,7 @@ struct WindowsTextInputComponentView void FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) noexcept override; static facebook::react::SharedViewProps defaultProps() noexcept; const facebook::react::WindowsTextInputProps &windowsTextInputProps() const noexcept; - void HandleCommand(winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept - override; + void HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept override; void OnRenderingDeviceLost() noexcept override; void onLostFocus(const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept override; void onGotFocus(const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept override; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp index b4550b8ab39..b678219c06c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp @@ -122,7 +122,9 @@ HRESULT UiaSetFocusHelper(::Microsoft::ReactNative::ReactTaggedView &view) noexc if (rootCV == nullptr) return UIA_E_ELEMENTNOTAVAILABLE; - return rootCV->TrySetFocusedComponent(strongView) ? S_OK : E_FAIL; + return rootCV->TrySetFocusedComponent(strongView, winrt::Microsoft::ReactNative::FocusNavigationDirection::None) + ? S_OK + : E_FAIL; } bool WasUiaPropertyAdvised(winrt::com_ptr &providerSimple, PROPERTYID propId) noexcept { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp index 40d6c47f887..ff16d0ffded 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp @@ -37,8 +37,7 @@ winrt::Microsoft::ReactNative::ComponentView UnimplementedNativeViewComponentVie } void UnimplementedNativeViewComponentView::HandleCommand( - winrt::hstring commandName, - const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept { + const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept { // Do not call base to avoid unknown command asserts } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.h index 633004a4ce0..c73c2c7494e 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.h @@ -25,8 +25,7 @@ struct UnimplementedNativeViewComponentView facebook::react::LayoutMetrics const &layoutMetrics, facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept override; - void HandleCommand(winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) noexcept - override; + void HandleCommand(const winrt::Microsoft::ReactNative::HandleCommandArgs &args) noexcept override; UnimplementedNativeViewComponentView( const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext, diff --git a/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp b/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp index eefad5d3b83..4d7259c1543 100644 --- a/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/FabricUIManagerModule.cpp @@ -2,6 +2,8 @@ // Licensed under the MIT License. #include "pch.h" +#include "HandleCommandArgs.g.cpp" +#include "HandleCommandArgs.g.h" #include #include #include @@ -368,6 +370,28 @@ void FabricUIManager::schedulerDidRequestPreliminaryViewAllocation(const faceboo */ } +struct HandleCommandArgs : public winrt::Microsoft::ReactNative::implementation::HandleCommandArgsT { + HandleCommandArgs(winrt::hstring commandName, folly::dynamic const &arg) : m_commandName(commandName), m_args(arg) {} + + winrt::hstring CommandName() const noexcept { + return m_commandName; + } + winrt::Microsoft::ReactNative::IJSValueReader CommandArgs() const noexcept { + return winrt::make(m_args); + } + bool Handled() const noexcept { + return m_handled; + } + void Handled(bool value) noexcept { + m_handled = value; + } + + private: + folly::dynamic const &m_args; + const winrt::hstring m_commandName; + bool m_handled{false}; +}; + void FabricUIManager::schedulerDidDispatchCommand( facebook::react::ShadowView const &shadowView, std::string const &commandName, @@ -375,7 +399,7 @@ void FabricUIManager::schedulerDidDispatchCommand( if (m_context.UIDispatcher().HasThreadAccess()) { auto descriptor = m_registry.componentViewDescriptorWithTag(shadowView.tag); winrt::get_self(descriptor.view) - ->HandleCommand(winrt::to_hstring(commandName), winrt::make(arg)); + ->HandleCommand(winrt::make(winrt::to_hstring(commandName), arg)); } else { m_context.UIDispatcher().Post( [wkThis = weak_from_this(), commandName, tag = shadowView.tag, args = folly::dynamic(arg)]() { @@ -383,7 +407,7 @@ void FabricUIManager::schedulerDidDispatchCommand( auto view = pThis->m_registry.findComponentViewWithTag(tag); if (view) { winrt::get_self(view)->HandleCommand( - winrt::to_hstring(commandName), winrt::make(args)); + winrt::make(winrt::to_hstring(commandName), args)); } } }); diff --git a/vnext/Microsoft.ReactNative/IReactViewComponentBuilder.idl b/vnext/Microsoft.ReactNative/IReactViewComponentBuilder.idl index 33617e4eec5..4de452ce1d9 100644 --- a/vnext/Microsoft.ReactNative/IReactViewComponentBuilder.idl +++ b/vnext/Microsoft.ReactNative/IReactViewComponentBuilder.idl @@ -47,6 +47,13 @@ namespace Microsoft.ReactNative UInt32 Index { get; }; }; + [experimental] + runtimeclass HandleCommandArgs { + String CommandName { get; }; + IJSValueReader CommandArgs { get; }; + Boolean Handled; + }; + [experimental] DOC_STRING("A delegate that creates a @IComponentProps object for an instance of @ViewProps. See @IReactViewComponentBuilder.SetCreateProps") delegate IComponentProps ViewPropsFactory(ViewProps props); @@ -71,7 +78,7 @@ namespace Microsoft.ReactNative delegate void ComponentViewInitializer(ComponentView view); [experimental] - delegate void HandleCommandDelegate(ComponentView source, String commandName, IJSValueReader args); + delegate void HandleCommandDelegate(ComponentView source, HandleCommandArgs args); [experimental] delegate void UpdateFinalizerDelegate(ComponentView source, ComponentViewUpdateMask updateMask);