diff --git a/change/react-native-windows-4046d3e2-cc00-459e-b62d-4489b3ef14d1.json b/change/react-native-windows-4046d3e2-cc00-459e-b62d-4489b3ef14d1.json new file mode 100644 index 00000000000..d16b5587b17 --- /dev/null +++ b/change/react-native-windows-4046d3e2-cc00-459e-b62d-4489b3ef14d1.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[Fabric] Fix for Text and TextInput focus issue with screen readers.", + "packageName": "react-native-windows", + "email": "kvineeth@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-b93a8310-8489-4f09-8497-eb5807c505b9.json b/change/react-native-windows-b93a8310-8489-4f09-8497-eb5807c505b9.json new file mode 100644 index 00000000000..c5711a3aefc --- /dev/null +++ b/change/react-native-windows-b93a8310-8489-4f09-8497-eb5807c505b9.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[Fabric] Implement announceForAccessibility in AccessibilityInfo Module", + "packageName": "react-native-windows", + "email": "kvineeth@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-bcf77be5-d1cd-4b1a-9756-3de4607890e5.json b/change/react-native-windows-bcf77be5-d1cd-4b1a-9756-3de4607890e5.json new file mode 100644 index 00000000000..6ebe164b659 --- /dev/null +++ b/change/react-native-windows-bcf77be5-d1cd-4b1a-9756-3de4607890e5.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[Fabric] Raising UIA Event if Toggle State Changes in Switch Component", + "packageName": "react-native-windows", + "email": "kvineeth@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionTextRangeProvider.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionTextRangeProvider.cpp index 1f69c84b3df..e2260913e15 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionTextRangeProvider.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionTextRangeProvider.cpp @@ -18,14 +18,30 @@ CompositionTextRangeProvider::CompositionTextRangeProvider( } HRESULT __stdcall CompositionTextRangeProvider::Clone(ITextRangeProvider **pRetVal) { - // no-op - *pRetVal = nullptr; + if (pRetVal == nullptr) + return E_POINTER; + + auto clone = winrt::make( + m_view.view().as(), m_parentProvider.get()); + *pRetVal = clone.detach(); return S_OK; } HRESULT __stdcall CompositionTextRangeProvider::Compare(ITextRangeProvider *range, BOOL *pRetVal) { - // no-op - *pRetVal = false; + if (pRetVal == nullptr) + return E_POINTER; + if (range == nullptr) { + *pRetVal = FALSE; + return S_OK; + } + + // Try to cast to our type , considering provider only supports a single range per view + auto other = dynamic_cast(range); + if (other && other->m_view.view() == m_view.view()) { + *pRetVal = TRUE; + } else { + *pRetVal = FALSE; + } return S_OK; } @@ -34,7 +50,10 @@ HRESULT __stdcall CompositionTextRangeProvider::CompareEndpoints( ITextRangeProvider *targetRange, TextPatternRangeEndpoint targetEndpoint, int *pRetVal) { - // no-op + if (pRetVal == nullptr) + return E_POINTER; + + // For a single-range provider, always equal: *pRetVal = 0; return S_OK; } @@ -98,13 +117,13 @@ HRESULT __stdcall CompositionTextRangeProvider::GetAttributeValue(TEXTATTRIBUTEI textTransform = props->textAttributes.textTransform.value(); } if (fontVariant == facebook::react::FontVariant::SmallCaps) { - return CapStyle_SmallCap; + pRetVal->lVal = CapStyle_SmallCap; } else if (textTransform == facebook::react::TextTransform::Capitalize) { - return CapStyle_Titling; + pRetVal->lVal = CapStyle_Titling; } else if (textTransform == facebook::react::TextTransform::Lowercase) { - return CapStyle_None; + pRetVal->lVal = CapStyle_None; } else if (textTransform == facebook::react::TextTransform::Uppercase) { - return CapStyle_AllCap; + pRetVal->lVal = CapStyle_AllCap; } } else if (attributeId == UIA_FontNameAttributeId) { pRetVal->vt = VT_BSTR; @@ -282,6 +301,8 @@ HRESULT __stdcall CompositionTextRangeProvider::ScrollIntoView(BOOL alignToTop) return S_OK; } +// All the below methods should be implemented once the selection comes for paragraph and TextInput + HRESULT __stdcall CompositionTextRangeProvider::AddToSelection() { // no-op return S_OK; diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp index 63829c6adab..d1424fa0ebd 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp @@ -40,6 +40,14 @@ namespace winrt::Microsoft::ReactNative::implementation { +ReactPropertyId>> +ReactNativeIsland::LastFocusedReactNativeIslandProperty() noexcept { + static const ReactPropertyId>> + prop{L"ReactNative.Composition", L"ReactNativeIsland"}; + return prop; +} constexpr float loadingActivitySize = 12.0f; constexpr float loadingActivityHorizontalOffset = 16.0f; constexpr float loadingBarHeight = 36.0f; @@ -861,6 +869,20 @@ winrt::Microsoft::UI::Content::ContentIsland ReactNativeIsland::Island() { } } }); + focusController.GotFocus( + [weakThis = get_weak()](const auto &sender, const winrt::Microsoft::UI::Input::FocusChangedEventArgs &args) { + if (auto pThis = weakThis.get()) { + // Set the island to React context so it can be accessed by native modules + if (pThis->m_context && pThis->m_island) { + auto properties = pThis->m_context.Properties(); + properties.Set( + ReactNativeIsland::LastFocusedReactNativeIslandProperty(), + winrt::Microsoft::ReactNative::ReactNonAbiValue< + winrt::weak_ref>{ + std::in_place, weakThis}); + } + } + }); // ContentIsland does not support weak_ref, so we cannot use auto_revoke for these events m_islandAutomationProviderRequestedToken = m_island.AutomationProviderRequested( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h index 0a306125890..2b959123387 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h @@ -49,6 +49,9 @@ struct ReactNativeIsland ~ReactNativeIsland() noexcept; ReactNativeIsland(const winrt::Microsoft::UI::Composition::Compositor &compositor) noexcept; + static ReactPropertyId>> + LastFocusedReactNativeIslandProperty() noexcept; ReactNativeIsland(const winrt::Microsoft::ReactNative::Composition::PortalComponentView &portal) noexcept; static winrt::Microsoft::ReactNative::ReactNativeIsland CreatePortal( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp index 129211ebf05..7a79a2a7e88 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/SwitchComponentView.cpp @@ -9,6 +9,7 @@ #include #include "CompositionDynamicAutomationProvider.h" #include "RootComponentView.h" +#include "UiaHelpers.h" namespace winrt::Microsoft::ReactNative::Composition::implementation { @@ -80,6 +81,16 @@ void SwitchComponentView::updateProps( m_visualUpdateRequired = true; } + if (oldViewProps.value != newViewProps.value) { + if (UiaClientsAreListening()) { + winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty( + EnsureUiaProvider(), + UIA_ToggleToggleStatePropertyId, + oldViewProps.value ? ToggleState_On : ToggleState_Off, + newViewProps.value ? ToggleState_On : ToggleState_Off); + } + } + Super::updateProps(props, oldProps); } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp index 4d2ff6ae8a0..20c34577355 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp @@ -166,6 +166,15 @@ void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, bool old UiaRaiseAutomationPropertyChangedEvent(spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue)); } +void UpdateUiaProperty(winrt::IInspectable provider, PROPERTYID propId, int oldValue, int newValue) noexcept { + auto spProviderSimple = provider.try_as(); + + if (spProviderSimple == nullptr || oldValue == newValue || !WasUiaPropertyAdvised(spProviderSimple, propId)) + return; + + UiaRaiseAutomationPropertyChangedEvent(spProviderSimple.get(), propId, CComVariant(oldValue), CComVariant(newValue)); +} + void UpdateUiaProperty( winrt::IInspectable provider, PROPERTYID propId, diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h index 5f4494a607c..b1f0e268c00 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h @@ -29,6 +29,12 @@ void UpdateUiaProperty( bool oldValue, bool newValue) noexcept; +void UpdateUiaProperty( + winrt::Windows::Foundation::IInspectable provider, + PROPERTYID propId, + int oldValue, + int newValue) noexcept; + void UpdateUiaProperty( winrt::Windows::Foundation::IInspectable provider, PROPERTYID propId, diff --git a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp index 9e287550eee..2c6915cd66e 100644 --- a/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp +++ b/vnext/Microsoft.ReactNative/Modules/AccessibilityInfoModule.cpp @@ -7,6 +7,8 @@ #include #include #include +#else +#include #endif #include #include @@ -79,6 +81,33 @@ void AccessibilityInfo::announceForAccessibility(std::wstring announcement) noex xaml::Automation::Peers::AutomationNotificationProcessing::ImportantMostRecent, hstr, hstr); +#else + if (auto weakIslandWrapper = context.Properties().Get( + winrt::Microsoft::ReactNative::implementation::ReactNativeIsland::LastFocusedReactNativeIslandProperty())) { + if (auto weakIsland = weakIslandWrapper.Value()) { + if (auto reactNativeIsland = weakIsland.get()) { + if (auto uiaprovider = reactNativeIsland->GetUiaProvider()) { + if (auto rawProvider = uiaprovider.try_as()) { + // Convert announcement to BSTR for UIA + winrt::hstring hstrAnnouncement{announcement}; + auto bstrAnnouncement = SysAllocString(hstrAnnouncement.c_str()); + if (bstrAnnouncement) { + // Raise the UIA notification event + HRESULT hr = UiaRaiseNotificationEvent( + rawProvider.get(), + NotificationKind_Other, + NotificationProcessing_ImportantMostRecent, + bstrAnnouncement, + bstrAnnouncement); + // Clean up BSTRs + SysFreeString(bstrAnnouncement); + } + } + } + } + } + } + #endif }); }