From 0a8a100038b95658501d6103ab7c080b88616532 Mon Sep 17 00:00:00 2001 From: vineethkuttan <66076509+vineethkuttan@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:46:23 +0530 Subject: [PATCH 1/7] =?UTF-8?q?Enable=20Fragment=E2=80=91Based=20Automatio?= =?UTF-8?q?n=20for=20ChildSite=20Without=20XAML=20Islands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/sample-app-fabric/App.tsx | 38 ++- packages/sample-app-fabric/package.json | 3 +- .../packages.lock.json | 10 + .../windows/SampleAppFabric.sln | 14 + .../AutolinkedNativeModules.g.cpp | 6 +- .../AutolinkedNativeModules.g.targets | 4 + .../SampleAppFabric/packages.lock.json | 9 + .../src/PickerXamlNativeComponent.ts | 43 +++ packages/sample-custom-component/src/index.ts | 3 + .../SampleCustomComponent/PickerXaml.cpp | 230 ++++++++++++++++ .../SampleCustomComponent/PickerXaml.h | 10 + .../ReactPackageProvider.cpp | 2 + .../SampleCustomComponent.vcxproj | 2 + .../SampleCustomComponent/PickerXaml.g.h | 247 ++++++++++++++++++ .../ContentIslandComponentView.cpp | 74 +++++- .../Composition/ContentIslandComponentView.h | 4 + 16 files changed, 691 insertions(+), 8 deletions(-) create mode 100644 packages/sample-custom-component/src/PickerXamlNativeComponent.ts create mode 100644 packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp create mode 100644 packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h create mode 100644 packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h diff --git a/packages/sample-app-fabric/App.tsx b/packages/sample-app-fabric/App.tsx index 3ac27a50420..a9abdcfaa67 100644 --- a/packages/sample-app-fabric/App.tsx +++ b/packages/sample-app-fabric/App.tsx @@ -5,20 +5,52 @@ * @format */ -import React from 'react'; -import {SafeAreaView, StatusBar, useColorScheme} from 'react-native'; +import React, {useState} from 'react'; +import { + SafeAreaView, + StatusBar, + useColorScheme, + Text, + View, +} from 'react-native'; import {NewAppScreen} from '@react-native/new-app-screen'; +import {PickerXaml} from 'sample-custom-component'; + +const pickerItems = [ + {label: 'JavaScript', value: 'js'}, + {label: 'TypeScript', value: 'ts'}, + {label: 'Python', value: 'py'}, + {label: 'C++', value: 'cpp'}, + {label: 'Rust', value: 'rs'}, +]; function App(): React.JSX.Element { const isDarkMode = useColorScheme() === 'dark'; + const [selectedIndex, setSelectedIndex] = useState(3); // Default to C++ + + const backgroundColor = isDarkMode ? '#1a1a1a' : '#f5f5f5'; + const textColor = isDarkMode ? '#ffffff' : '#000000'; return ( - + + + + Selected: {pickerItems[selectedIndex]?.label} + + { + setSelectedIndex(event.nativeEvent.itemIndex); + }} + /> + ); diff --git a/packages/sample-app-fabric/package.json b/packages/sample-app-fabric/package.json index 25c7f5d119e..0c0607b4731 100644 --- a/packages/sample-app-fabric/package.json +++ b/packages/sample-app-fabric/package.json @@ -18,7 +18,8 @@ "@typescript-eslint/parser": "^7.1.1", "react": "^19.1.1", "react-native": "0.82.0-nightly-20250902-9731e8ebc", - "react-native-windows": "^0.0.0-canary.1031" + "react-native-windows": "^0.0.0-canary.1031", + "sample-custom-component": "0.0.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json b/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json index d4f48cf99bc..c4416e656d3 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json +++ b/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json @@ -185,6 +185,16 @@ "type": "Project", "dependencies": { "Microsoft.JavaScript.Hermes": "[0.0.0-2512.22001-bc3d0ed7, )", + "Microsoft.ReactNative": "[1.0.0, )", + "Microsoft.VCRTForwarders.140": "[1.0.2-rc, )", + "Microsoft.WindowsAppSDK": "[1.8.251106002, )", + "SampleCustomComponent": "[1.0.0, )", + "boost": "[1.83.0, )" + } + }, + "samplecustomcomponent": { + "type": "Project", + "dependencies": { "Microsoft.ReactNative": "[1.0.0, )", "Microsoft.VCRTForwarders.140": "[1.0.2-rc, )", "Microsoft.WindowsAppSDK": "[1.8.251106002, )", diff --git a/packages/sample-app-fabric/windows/SampleAppFabric.sln b/packages/sample-app-fabric/windows/SampleAppFabric.sln index 3335ca845cd..e562e1f033e 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric.sln +++ b/packages/sample-app-fabric/windows/SampleAppFabric.sln @@ -33,6 +33,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\..\..\vnext\Mso\M EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\..\..\vnext\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleCustomComponent", "..\..\..\node_modules\sample-custom-component\windows\SampleCustomComponent\SampleCustomComponent.vcxproj", "{A8DA218C-4CB5-48CB-A9EE-9E6337165D07}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ..\..\..\vnext\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 @@ -150,6 +152,18 @@ Global {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.ActiveCfg = Release|Win32 {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Build.0 = Release|Win32 {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Deploy.0 = Release|Win32 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x64.ActiveCfg = Debug|x64 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x64.Build.0 = Debug|x64 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x86.ActiveCfg = Debug|Win32 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x86.Build.0 = Debug|Win32 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|ARM64.Build.0 = Debug|ARM64 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x64.ActiveCfg = Release|x64 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x64.Build.0 = Release|x64 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x86.ActiveCfg = Release|Win32 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x86.Build.0 = Release|Win32 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|ARM64.ActiveCfg = Release|ARM64 + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|ARM64.Build.0 = Release|ARM64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp index 5821cfddc55..b4f9b7ce9c8 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp +++ b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp @@ -3,12 +3,16 @@ #include "pch.h" #include "AutolinkedNativeModules.g.h" +// Includes from sample-custom-component +#include + namespace winrt::Microsoft::ReactNative { void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) { - UNREFERENCED_PARAMETER(packageProviders); + // IReactPackageProviders from sample-custom-component + packageProviders.Append(winrt::SampleCustomComponent::ReactPackageProvider()); } } diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets index 6a85ec51cd9..35f260ab39e 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets +++ b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets @@ -2,5 +2,9 @@ + + + {A8DA218C-4CB5-48CB-A9EE-9E6337165D07} + diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json b/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json index 8af9287d979..6c00291fc28 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json +++ b/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json @@ -190,6 +190,15 @@ "Folly": "[1.0.0, )", "boost": "[1.83.0, )" } + }, + "samplecustomcomponent": { + "type": "Project", + "dependencies": { + "Microsoft.ReactNative": "[1.0.0, )", + "Microsoft.VCRTForwarders.140": "[1.0.2-rc, )", + "Microsoft.WindowsAppSDK": "[1.8.251106002, )", + "boost": "[1.83.0, )" + } } }, "native,Version=v0.0/win": { diff --git a/packages/sample-custom-component/src/PickerXamlNativeComponent.ts b/packages/sample-custom-component/src/PickerXamlNativeComponent.ts new file mode 100644 index 00000000000..48d1a7ebc21 --- /dev/null +++ b/packages/sample-custom-component/src/PickerXamlNativeComponent.ts @@ -0,0 +1,43 @@ +import {codegenNativeComponent} from 'react-native'; +import type { ViewProps } from 'react-native'; +import type { + Int32, + WithDefault, + BubblingEventHandler, +} from 'react-native/Libraries/Types/CodegenTypes'; + +/** + * Represents a single item in the PickerXaml. + */ +export type PickerXamlItem = Readonly<{ + label: string; + value?: string; +}>; + +/** + * Event payload when the picker selection changes. + */ +export type PickerXamlChangeEvent = Readonly<{ + value: string; + itemIndex: Int32; + text: string; +}>; + +export interface PickerXamlProps extends ViewProps { + /** + * Array of picker items to display. + */ + items: ReadonlyArray; + + /** + * The index of the currently selected item. + */ + selectedIndex?: WithDefault; + + /** + * Callback when selection changes. + */ + onPickerSelect?: BubblingEventHandler; +} + +export default codegenNativeComponent('PickerXaml'); diff --git a/packages/sample-custom-component/src/index.ts b/packages/sample-custom-component/src/index.ts index 49b2bd07d43..69288ff72c6 100644 --- a/packages/sample-custom-component/src/index.ts +++ b/packages/sample-custom-component/src/index.ts @@ -7,10 +7,13 @@ import CalendarView from './FabricXamlCalendarViewNativeComponent' import CustomAccessibility from './CustomAccessibilityNativeComponent'; +import PickerXaml from './PickerXamlNativeComponent'; + export { CustomAccessibility, DrawingIsland, MovingLight, MovingLightHandle, CalendarView, + PickerXaml }; \ No newline at end of file diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp new file mode 100644 index 00000000000..8f28b61d0ce --- /dev/null +++ b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp @@ -0,0 +1,230 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "PickerXaml.h" + +#if defined(RNW_NEW_ARCH) + +#include "codegen/react/components/SampleCustomComponent/PickerXaml.g.h" + +#include +#include +#include +#include +#include +#include + +namespace winrt::SampleCustomComponent { + +// State to store the measured size +struct PickerXamlStateData : winrt::implements { + PickerXamlStateData(winrt::Windows::Foundation::Size ds) : desiredSize(ds) {} + winrt::Windows::Foundation::Size desiredSize; +}; + +struct PickerXamlComponentView + : winrt::implements, + Codegen::BasePickerXaml { + + void InitializeContentIsland( + const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView& islandView) { + // Create ComboBox for picker functionality + m_comboBox = winrt::Microsoft::UI::Xaml::Controls::ComboBox(); + m_comboBox.HorizontalAlignment( + winrt::Microsoft::UI::Xaml::HorizontalAlignment::Stretch); + + // For editable ComboBox, trigger selection change always (not just on commit) + m_comboBox.SelectionChangedTrigger( + winrt::Microsoft::UI::Xaml::Controls::ComboBoxSelectionChangedTrigger::Always); + + // Listen for size changes on the comboBox + m_comboBox.SizeChanged([this](auto const& /*sender*/, auto const& /*args*/) { + RefreshSize(); + }); + + // Listen for selection changes + m_selectionChangedRevoker = m_comboBox.SelectionChanged( + winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { + EmitPickerSelectEvent(); + }); + + // Listen for text submitted (when user presses Enter in editable mode) + m_textSubmittedRevoker = m_comboBox.TextSubmitted( + winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { + EmitPickerSelectEvent(); + }); + + m_island = winrt::Microsoft::UI::Xaml::XamlIsland{}; + m_island.Content(m_comboBox); + islandView.Connect(m_island.ContentIsland()); + m_islandView = winrt::make_weak(islandView); + } + + void UpdateProps( + const winrt::Microsoft::ReactNative::ComponentView& view, + const winrt::com_ptr& newProps, + const winrt::com_ptr& oldProps) noexcept override { + BasePickerXaml::UpdateProps(view, newProps, oldProps); + + // Suspend event handlers during programmatic updates to avoid triggering + // change events. Using RAII ensures handlers are always re-attached. + WithEventsSuspended([&]() { + // Always update items on first render, or when size changes + // (We can't compare items directly as the struct lacks operator==) + if (!oldProps || oldProps->items.size() != newProps->items.size() || m_comboBox.Items().Size() == 0) { + m_comboBox.Items().Clear(); + m_items.clear(); + + for (const auto& item : newProps->items) { + // Store item data + m_items.push_back(item); + + // Add item to ComboBox + auto comboBoxItem = winrt::Microsoft::UI::Xaml::Controls::ComboBoxItem(); + comboBoxItem.Content(winrt::box_value(winrt::to_hstring(item.label))); + m_comboBox.Items().Append(comboBoxItem); + } + } + + // Always update selected index on first render or when changed + if (!oldProps || oldProps->selectedIndex != newProps->selectedIndex || m_comboBox.SelectedIndex() != newProps->selectedIndex) { + const int32_t selectedIndex = newProps->selectedIndex; + if (selectedIndex >= 0 && selectedIndex < static_cast(m_comboBox.Items().Size())) { + m_comboBox.SelectedIndex(selectedIndex); + } else { + m_comboBox.SelectedIndex(-1); + } + } + }); + + RefreshSize(); + } + + void UpdateState( + const winrt::Microsoft::ReactNative::ComponentView& /*view*/, + const winrt::Microsoft::ReactNative::IComponentState& newState) noexcept override { + m_state = newState; + } + + private: + void RefreshSize() { + m_comboBox.Measure(winrt::Windows::Foundation::Size{ + std::numeric_limits::infinity(), + std::numeric_limits::infinity() + }); + + const auto desiredSize = m_comboBox.DesiredSize(); + + if (m_state) { + const auto currentState = winrt::get_self(m_state.Data()); + if (desiredSize != currentState->desiredSize) { + m_state.UpdateStateWithMutation([desiredSize](winrt::Windows::Foundation::IInspectable /*data*/) { + return winrt::make(desiredSize); + }); + } + } + } + + void EmitPickerSelectEvent() { + if (auto eventEmitter = this->EventEmitter()) { + const int32_t selectedIndex = m_comboBox.SelectedIndex(); + + Codegen::PickerXaml_OnPickerSelect eventArgs; + eventArgs.itemIndex = selectedIndex; + + // Get the selected item value and text if available + if (selectedIndex >= 0 && selectedIndex < static_cast(m_items.size())) { + eventArgs.value = m_items[selectedIndex].value.value_or(""); + eventArgs.text = m_items[selectedIndex].label; + } else { + eventArgs.text = ""; + eventArgs.value = ""; + } + + eventEmitter->onPickerSelect(eventArgs); + } + } + + winrt::weak_ref m_islandView; + winrt::Microsoft::UI::Xaml::XamlIsland m_island{nullptr}; + winrt::Microsoft::UI::Xaml::Controls::ComboBox m_comboBox{nullptr}; + winrt::Microsoft::ReactNative::IComponentState m_state{nullptr}; + std::vector m_items; + winrt::Microsoft::UI::Xaml::Controls::ComboBox::SelectionChanged_revoker m_selectionChangedRevoker; + winrt::Microsoft::UI::Xaml::Controls::ComboBox::TextSubmitted_revoker m_textSubmittedRevoker; + + // RAII helper to temporarily suspend event handlers during programmatic updates. + // This avoids triggering change events when we're setting values from props. + template + void WithEventsSuspended(TAction action) { + m_selectionChangedRevoker.revoke(); + m_textSubmittedRevoker.revoke(); + + action(); + + m_selectionChangedRevoker = m_comboBox.SelectionChanged( + winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { + EmitPickerSelectEvent(); + }); + m_textSubmittedRevoker = m_comboBox.TextSubmitted( + winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { + EmitPickerSelectEvent(); + }); + } +}; + +} // namespace winrt::SampleCustomComponent + +void RegisterPickerXamlComponentView( + winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) { + winrt::SampleCustomComponent::Codegen::RegisterPickerXamlNativeComponent< + winrt::SampleCustomComponent::PickerXamlComponentView>( + packageBuilder, + [](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) { + builder.as().XamlSupport(true); + // Use SetContentIslandComponentViewInitializer + builder.SetContentIslandComponentViewInitializer( + [](const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept { + auto userData = winrt::make_self(); + userData->InitializeContentIsland(islandView); + islandView.UserData(*userData); + }); + // Set up initial state with zero size + builder.as().SetInitialStateDataFactory( + [](const winrt::Microsoft::ReactNative::IComponentProps& /*props*/) noexcept { + return winrt::make( + winrt::Windows::Foundation::Size{0, 0}); + }); + + // Register the measure function - reads from state + builder.as().SetMeasureContentHandler( + [](winrt::Microsoft::ReactNative::ShadowNode const &shadowNode, + winrt::Microsoft::ReactNative::LayoutContext const &, + winrt::Microsoft::ReactNative::LayoutConstraints const &) noexcept { + const auto currentState = + winrt::get_self(shadowNode.StateData()); + + if (currentState && currentState->desiredSize.Width > 0) { + // Return the measured size from state + return currentState->desiredSize; + } + + // Return a default size if we don't have a measurement yet + return winrt::Windows::Foundation::Size{100, 32}; + }); + + // Handle state updates + builder.as().SetUpdateStateHandler( + [](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::IComponentState &newState) { + const auto islandView = + view.as(); + const auto userData = + islandView.UserData().as(); + userData->UpdateState(view, newState); + }); + }); +} + +#endif // defined(RNW_NEW_ARCH) diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h new file mode 100644 index 00000000000..dad8f0a44af --- /dev/null +++ b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#if defined(RNW_NEW_ARCH) + +void RegisterPickerXamlComponentView(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder); + +#endif // defined(RNW_NEW_ARCH) diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp b/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp index 29f8c8905e1..b9045b1f6e2 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp +++ b/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp @@ -11,6 +11,7 @@ #include "CustomAccessibility.h" #include "DrawingIsland.h" #include "MovingLight.h" +#include "PickerXaml.h" using namespace winrt::Microsoft::ReactNative; @@ -24,6 +25,7 @@ void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuil RegisterMovingLightNativeComponent(packageBuilder); RegisterCalendarViewComponentView(packageBuilder); RegisterCustomAccessibilityComponentView(packageBuilder); + RegisterPickerXamlComponentView(packageBuilder); #endif // #ifdef RNW_NEW_ARCH } diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj b/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj index 2a9dae1e5fe..4cedd6c57e9 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj +++ b/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj @@ -102,6 +102,7 @@ + DrawingIsland.idl @@ -115,6 +116,7 @@ + Create diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h new file mode 100644 index 00000000000..5c67b58b5e5 --- /dev/null +++ b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h @@ -0,0 +1,247 @@ + +/* + * This file is auto-generated from PickerXamlNativeComponent spec file in flow / TypeScript. + */ +// clang-format off +#pragma once + +#include + +#ifdef RNW_NEW_ARCH +#include + +#include +#include +#endif // #ifdef RNW_NEW_ARCH + +#ifdef RNW_NEW_ARCH + +namespace winrt::SampleCustomComponent::Codegen { + +REACT_STRUCT(PickerXamlSpec_PickerXamlProps_items) +struct PickerXamlSpec_PickerXamlProps_items { + REACT_FIELD(label) + std::string label; + + REACT_FIELD(value) + std::optional value; +}; + +REACT_STRUCT(PickerXamlProps) +struct PickerXamlProps : winrt::implements { + PickerXamlProps(winrt::Microsoft::ReactNative::ViewProps props, const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) + : ViewProps(props) + { + if (cloneFrom) { + auto cloneFromProps = cloneFrom.as(); + items = cloneFromProps->items; + selectedIndex = cloneFromProps->selectedIndex; + } + } + + void SetProp(uint32_t hash, winrt::hstring propName, winrt::Microsoft::ReactNative::IJSValueReader value) noexcept { + winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this); + } + + REACT_FIELD(items) + std::vector items; + + REACT_FIELD(selectedIndex) + int32_t selectedIndex{-1}; + + const winrt::Microsoft::ReactNative::ViewProps ViewProps; +}; + +REACT_STRUCT(PickerXaml_OnPickerSelect) +struct PickerXaml_OnPickerSelect { + REACT_FIELD(value) + std::string value; + + REACT_FIELD(itemIndex) + int32_t itemIndex{}; + + REACT_FIELD(text) + std::string text; +}; + +struct PickerXamlEventEmitter { + PickerXamlEventEmitter(const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) + : m_eventEmitter(eventEmitter) {} + + using OnPickerSelect = PickerXaml_OnPickerSelect; + + void onPickerSelect(OnPickerSelect &value) const { + m_eventEmitter.DispatchEvent(L"pickerSelect", [value](const winrt::Microsoft::ReactNative::IJSValueWriter writer) { + winrt::Microsoft::ReactNative::WriteValue(writer, value); + }); + } + + private: + winrt::Microsoft::ReactNative::EventEmitter m_eventEmitter{nullptr}; +}; + +template +struct BasePickerXaml { + + virtual void UpdateProps( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::com_ptr &newProps, + const winrt::com_ptr &/*oldProps*/) noexcept { + m_props = newProps; + } + + // UpdateLayoutMetrics will only be called if this method is overridden + virtual void UpdateLayoutMetrics( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::LayoutMetrics &/*newLayoutMetrics*/, + const winrt::Microsoft::ReactNative::LayoutMetrics &/*oldLayoutMetrics*/) noexcept { + } + + // UpdateState will only be called if this method is overridden + virtual void UpdateState( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::IComponentState &/*newState*/) noexcept { + } + + virtual void UpdateEventEmitter(const std::shared_ptr &eventEmitter) noexcept { + m_eventEmitter = eventEmitter; + } + + // MountChildComponentView will only be called if this method is overridden + virtual void MountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &/*args*/) noexcept { + } + + // UnmountChildComponentView will only be called if this method is overridden + virtual void UnmountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &/*args*/) noexcept { + } + + // Initialize will only be called if this method is overridden + virtual void Initialize(const winrt::Microsoft::ReactNative::ComponentView &/*view*/) noexcept { + } + + // CreateVisual will only be called if this method is overridden + virtual winrt::Microsoft::UI::Composition::Visual CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + return view.as().Compositor().CreateSpriteVisual(); + } + + // FinalizeUpdate will only be called if this method is overridden + virtual void FinalizeUpdate(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + winrt::Microsoft::ReactNative::ComponentViewUpdateMask /*mask*/) noexcept { + } + + // CreateAutomationPeer will only be called if this method is overridden + virtual winrt::Windows::Foundation::IInspectable CreateAutomationPeer(const winrt::Microsoft::ReactNative::ComponentView & /*view*/, + const winrt::Microsoft::ReactNative::CreateAutomationPeerArgs& /*args*/) noexcept { + return nullptr; + } + + + + const std::shared_ptr& EventEmitter() const { return m_eventEmitter; } + const winrt::com_ptr& Props() const { return m_props; } + +private: + winrt::com_ptr m_props; + std::shared_ptr m_eventEmitter; +}; + +template +void RegisterPickerXamlNativeComponent( + winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder, + std::function builderCallback) noexcept { + packageBuilder.as().AddViewComponent( + L"PickerXaml", [builderCallback](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept { + auto compBuilder = builder.as(); + + builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props, + const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) noexcept { + return winrt::make(props, cloneFrom); + }); + + builder.SetUpdatePropsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::IComponentProps &newProps, + const winrt::Microsoft::ReactNative::IComponentProps &oldProps) noexcept { + auto userData = view.UserData().as(); + userData->UpdateProps(view, newProps ? newProps.as() : nullptr, oldProps ? oldProps.as() : nullptr); + }); + + compBuilder.SetUpdateLayoutMetricsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::LayoutMetrics &newLayoutMetrics, + const winrt::Microsoft::ReactNative::LayoutMetrics &oldLayoutMetrics) noexcept { + auto userData = view.UserData().as(); + userData->UpdateLayoutMetrics(view, newLayoutMetrics, oldLayoutMetrics); + }); + + builder.SetUpdateEventEmitterHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) noexcept { + auto userData = view.UserData().as(); + userData->UpdateEventEmitter(std::make_shared(eventEmitter)); + }); + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::FinalizeUpdate != &BasePickerXaml::FinalizeUpdate) { + builder.SetFinalizeUpdateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + winrt::Microsoft::ReactNative::ComponentViewUpdateMask mask) noexcept { + auto userData = view.UserData().as(); + userData->FinalizeUpdate(view, mask); + }); + } + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::UpdateState != &BasePickerXaml::UpdateState) { + builder.SetUpdateStateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept { + auto userData = view.UserData().as(); + userData->UpdateState(view, newState); + }); + } + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::MountChildComponentView != &BasePickerXaml::MountChildComponentView) { + builder.SetMountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept { + auto userData = view.UserData().as(); + return userData->MountChildComponentView(view, args); + }); + } + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::UnmountChildComponentView != &BasePickerXaml::UnmountChildComponentView) { + builder.SetUnmountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept { + auto userData = view.UserData().as(); + return userData->UnmountChildComponentView(view, args); + }); + } + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::CreateAutomationPeer != &BasePickerXaml::CreateAutomationPeer) { + builder.SetCreateAutomationPeerHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::CreateAutomationPeerArgs& args) noexcept { + auto userData = view.UserData().as(); + return userData->CreateAutomationPeer(view, args); + }); + } + + compBuilder.SetViewComponentViewInitializer([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + auto userData = winrt::make_self(); + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::Initialize != &BasePickerXaml::Initialize) { + userData->Initialize(view); + } + view.UserData(*userData); + }); + + if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::CreateVisual != &BasePickerXaml::CreateVisual) { + compBuilder.SetCreateVisualHandler([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + auto userData = view.UserData().as(); + return userData->CreateVisual(view); + }); + } + + // Allow app to further customize the builder + if (builderCallback) { + builderCallback(compBuilder); + } + }); +} + +} // namespace winrt::SampleCustomComponent::Codegen + +#endif // #ifdef RNW_NEW_ARCH diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp index 84d73ef2899..9fb73472fa3 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp @@ -171,6 +171,24 @@ ContentIslandComponentView::~ContentIslandComponentView() noexcept { m_navigationHost.DepartFocusRequested(m_navigationHostDepartFocusRequestedToken); m_navigationHostDepartFocusRequestedToken = {}; } + if (m_childSiteLink) { + if (m_fragmentRootAutomationProviderRequestedToken) { + m_childSiteLink.FragmentRootAutomationProviderRequested(m_fragmentRootAutomationProviderRequestedToken); + m_fragmentRootAutomationProviderRequestedToken = {}; + } + if (m_parentAutomationProviderRequestedToken) { + m_childSiteLink.ParentAutomationProviderRequested(m_parentAutomationProviderRequestedToken); + m_parentAutomationProviderRequestedToken = {}; + } + if (m_nextSiblingAutomationProviderRequestedToken) { + m_childSiteLink.NextSiblingAutomationProviderRequested(m_nextSiblingAutomationProviderRequestedToken); + m_nextSiblingAutomationProviderRequestedToken = {}; + } + if (m_previousSiblingAutomationProviderRequestedToken) { + m_childSiteLink.PreviousSiblingAutomationProviderRequested(m_previousSiblingAutomationProviderRequestedToken); + m_previousSiblingAutomationProviderRequestedToken = {}; + } + } if (m_islandToConnect) { m_islandToConnect.Close(); } @@ -215,10 +233,60 @@ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { // Use FrameworkBased to let the XamlIsland manage its own framework-level accessibility tree // and raise focus events naturally. This tells the system that the child island has its own // framework (WinUI/XAML) that manages automation. - m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FrameworkBased); + if (m_builder && m_builder->XamlSupport()) { + m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FrameworkBased); + } else { + // This automation mode must be set before connecting the child ContentIsland. + // It puts the child content into a mode where it won't own its own framework root. Instead, the child island's + // automation peers will use the same framework root as the automation peer of this ContentIslandComponentView. + m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FragmentBased); + + // These events are raised in response to the child ContentIsland asking for providers. + // For example, the ContentIsland.FragmentRootAutomationProvider property will return + // the provider we provide here in FragmentRootAutomationProviderRequested. + + // We capture "this" as a raw pointer because ContentIslandComponentView doesn't currently support weak ptrs. + // It's safe because we disconnect these events in the destructor. + + m_fragmentRootAutomationProviderRequestedToken = m_childSiteLink.FragmentRootAutomationProviderRequested( + [this]( + const winrt::Microsoft::UI::Content::IContentSiteAutomation &, + const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { + // The child island's fragment tree doesn't have its own fragment root. + // Here's how we can provide the correct fragment root to the child's UIA logic. + winrt::com_ptr fragmentRoot{nullptr}; + auto uiaProvider = this->EnsureUiaProvider(); + uiaProvider.as()->get_FragmentRoot(fragmentRoot.put()); + args.AutomationProvider(fragmentRoot.as()); + args.Handled(true); + }); + + m_parentAutomationProviderRequestedToken = m_childSiteLink.ParentAutomationProviderRequested( + [this]( + const winrt::Microsoft::UI::Content::IContentSiteAutomation &, + const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { + auto uiaProvider = this->EnsureUiaProvider(); + args.AutomationProvider(uiaProvider); + args.Handled(true); + }); + + m_nextSiblingAutomationProviderRequestedToken = m_childSiteLink.NextSiblingAutomationProviderRequested( + [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &, + const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { + // The ContentIsland will always be the one and only child of this node, so it won't have siblings. + args.AutomationProvider(nullptr); + args.Handled(true); + }); + + m_previousSiblingAutomationProviderRequestedToken = m_childSiteLink.PreviousSiblingAutomationProviderRequested( + [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &, + const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { + // The ContentIsland will always be the one and only child of this node, so it won't have siblings. + args.AutomationProvider(nullptr); + args.Handled(true); + }); + } - // When using FrameworkBased mode, we don't register automation callbacks - let the XamlIsland handle its own UIA - // tree. if (m_innerAutomationProvider) { m_innerAutomationProvider->SetChildSiteLink(m_childSiteLink); } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h index 7c85ee1cc01..e6f5fe8808a 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h @@ -70,6 +70,10 @@ struct ContentIslandComponentView : ContentIslandComponentViewT Date: Tue, 3 Feb 2026 21:37:55 +0530 Subject: [PATCH 2/7] change files --- ...ative-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json diff --git a/change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json b/change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json new file mode 100644 index 00000000000..26087baa119 --- /dev/null +++ b/change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Enable Fragment‑Based Automation for ChildSite Without XAML Islands", + "packageName": "react-native-windows", + "email": "66076509+vineethkuttan@users.noreply.github.com", + "dependentChangeType": "patch" +} From 0457d40eb2989d639e2accaa0d6a93b44d5d5afe Mon Sep 17 00:00:00 2001 From: vineethkuttan <66076509+vineethkuttan@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:59:34 +0530 Subject: [PATCH 3/7] expose childsite automation option --- .../SampleCustomComponent/PickerXaml.cpp | 76 +++++++------------ .../ContentIslandComponentView.cpp | 64 +++------------- .../ReactCompositionViewComponentBuilder.cpp | 10 +++ .../ReactCompositionViewComponentBuilder.h | 6 ++ .../IReactCompositionViewComponentBuilder.idl | 2 + 5 files changed, 56 insertions(+), 102 deletions(-) diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp index 8f28b61d0ce..f76cca0b9c6 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp +++ b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp @@ -8,10 +8,10 @@ #include "codegen/react/components/SampleCustomComponent/PickerXaml.g.h" -#include #include -#include +#include #include +#include #include #include @@ -23,37 +23,27 @@ struct PickerXamlStateData : winrt::implements, - Codegen::BasePickerXaml { - +struct PickerXamlComponentView : winrt::implements, + Codegen::BasePickerXaml { void InitializeContentIsland( - const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView& islandView) { + const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) { // Create ComboBox for picker functionality m_comboBox = winrt::Microsoft::UI::Xaml::Controls::ComboBox(); - m_comboBox.HorizontalAlignment( - winrt::Microsoft::UI::Xaml::HorizontalAlignment::Stretch); - + m_comboBox.HorizontalAlignment(winrt::Microsoft::UI::Xaml::HorizontalAlignment::Stretch); + // For editable ComboBox, trigger selection change always (not just on commit) - m_comboBox.SelectionChangedTrigger( - winrt::Microsoft::UI::Xaml::Controls::ComboBoxSelectionChangedTrigger::Always); + m_comboBox.SelectionChangedTrigger(winrt::Microsoft::UI::Xaml::Controls::ComboBoxSelectionChangedTrigger::Always); // Listen for size changes on the comboBox - m_comboBox.SizeChanged([this](auto const& /*sender*/, auto const& /*args*/) { - RefreshSize(); - }); + m_comboBox.SizeChanged([this](auto const & /*sender*/, auto const & /*args*/) { RefreshSize(); }); // Listen for selection changes m_selectionChangedRevoker = m_comboBox.SelectionChanged( - winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { - EmitPickerSelectEvent(); - }); + winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); // Listen for text submitted (when user presses Enter in editable mode) m_textSubmittedRevoker = m_comboBox.TextSubmitted( - winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { - EmitPickerSelectEvent(); - }); + winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); m_island = winrt::Microsoft::UI::Xaml::XamlIsland{}; m_island.Content(m_comboBox); @@ -62,9 +52,9 @@ struct PickerXamlComponentView } void UpdateProps( - const winrt::Microsoft::ReactNative::ComponentView& view, - const winrt::com_ptr& newProps, - const winrt::com_ptr& oldProps) noexcept override { + const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::com_ptr &newProps, + const winrt::com_ptr &oldProps) noexcept override { BasePickerXaml::UpdateProps(view, newProps, oldProps); // Suspend event handlers during programmatic updates to avoid triggering @@ -76,7 +66,7 @@ struct PickerXamlComponentView m_comboBox.Items().Clear(); m_items.clear(); - for (const auto& item : newProps->items) { + for (const auto &item : newProps->items) { // Store item data m_items.push_back(item); @@ -88,7 +78,8 @@ struct PickerXamlComponentView } // Always update selected index on first render or when changed - if (!oldProps || oldProps->selectedIndex != newProps->selectedIndex || m_comboBox.SelectedIndex() != newProps->selectedIndex) { + if (!oldProps || oldProps->selectedIndex != newProps->selectedIndex || + m_comboBox.SelectedIndex() != newProps->selectedIndex) { const int32_t selectedIndex = newProps->selectedIndex; if (selectedIndex >= 0 && selectedIndex < static_cast(m_comboBox.Items().Size())) { m_comboBox.SelectedIndex(selectedIndex); @@ -102,17 +93,15 @@ struct PickerXamlComponentView } void UpdateState( - const winrt::Microsoft::ReactNative::ComponentView& /*view*/, - const winrt::Microsoft::ReactNative::IComponentState& newState) noexcept override { + const winrt::Microsoft::ReactNative::ComponentView & /*view*/, + const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept override { m_state = newState; } private: void RefreshSize() { m_comboBox.Measure(winrt::Windows::Foundation::Size{ - std::numeric_limits::infinity(), - std::numeric_limits::infinity() - }); + std::numeric_limits::infinity(), std::numeric_limits::infinity()}); const auto desiredSize = m_comboBox.DesiredSize(); @@ -156,28 +145,23 @@ struct PickerXamlComponentView // RAII helper to temporarily suspend event handlers during programmatic updates. // This avoids triggering change events when we're setting values from props. - template + template void WithEventsSuspended(TAction action) { m_selectionChangedRevoker.revoke(); m_textSubmittedRevoker.revoke(); - + action(); - + m_selectionChangedRevoker = m_comboBox.SelectionChanged( - winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { - EmitPickerSelectEvent(); - }); + winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); m_textSubmittedRevoker = m_comboBox.TextSubmitted( - winrt::auto_revoke, [this](const auto& /*sender*/, const auto& /*args*/) { - EmitPickerSelectEvent(); - }); + winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); } }; } // namespace winrt::SampleCustomComponent -void RegisterPickerXamlComponentView( - winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) { +void RegisterPickerXamlComponentView(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) { winrt::SampleCustomComponent::Codegen::RegisterPickerXamlNativeComponent< winrt::SampleCustomComponent::PickerXamlComponentView>( packageBuilder, @@ -192,7 +176,7 @@ void RegisterPickerXamlComponentView( }); // Set up initial state with zero size builder.as().SetInitialStateDataFactory( - [](const winrt::Microsoft::ReactNative::IComponentProps& /*props*/) noexcept { + [](const winrt::Microsoft::ReactNative::IComponentProps & /*props*/) noexcept { return winrt::make( winrt::Windows::Foundation::Size{0, 0}); }); @@ -218,10 +202,8 @@ void RegisterPickerXamlComponentView( builder.as().SetUpdateStateHandler( [](const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::IComponentState &newState) { - const auto islandView = - view.as(); - const auto userData = - islandView.UserData().as(); + const auto islandView = view.as(); + const auto userData = islandView.UserData().as(); userData->UpdateState(view, newState); }); }); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp index 9fb73472fa3..d73b6df807e 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp @@ -230,63 +230,17 @@ void ContentIslandComponentView::prepareForRecycle() noexcept { } void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { - // Use FrameworkBased to let the XamlIsland manage its own framework-level accessibility tree - // and raise focus events naturally. This tells the system that the child island has its own - // framework (WinUI/XAML) that manages automation. - if (m_builder && m_builder->XamlSupport()) { - m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FrameworkBased); - } else { - // This automation mode must be set before connecting the child ContentIsland. - // It puts the child content into a mode where it won't own its own framework root. Instead, the child island's - // automation peers will use the same framework root as the automation peer of this ContentIslandComponentView. - m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FragmentBased); - - // These events are raised in response to the child ContentIsland asking for providers. - // For example, the ContentIsland.FragmentRootAutomationProvider property will return - // the provider we provide here in FragmentRootAutomationProviderRequested. - - // We capture "this" as a raw pointer because ContentIslandComponentView doesn't currently support weak ptrs. - // It's safe because we disconnect these events in the destructor. - - m_fragmentRootAutomationProviderRequestedToken = m_childSiteLink.FragmentRootAutomationProviderRequested( - [this]( - const winrt::Microsoft::UI::Content::IContentSiteAutomation &, - const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { - // The child island's fragment tree doesn't have its own fragment root. - // Here's how we can provide the correct fragment root to the child's UIA logic. - winrt::com_ptr fragmentRoot{nullptr}; - auto uiaProvider = this->EnsureUiaProvider(); - uiaProvider.as()->get_FragmentRoot(fragmentRoot.put()); - args.AutomationProvider(fragmentRoot.as()); - args.Handled(true); - }); - - m_parentAutomationProviderRequestedToken = m_childSiteLink.ParentAutomationProviderRequested( - [this]( - const winrt::Microsoft::UI::Content::IContentSiteAutomation &, - const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { - auto uiaProvider = this->EnsureUiaProvider(); - args.AutomationProvider(uiaProvider); - args.Handled(true); - }); - - m_nextSiblingAutomationProviderRequestedToken = m_childSiteLink.NextSiblingAutomationProviderRequested( - [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &, - const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { - // The ContentIsland will always be the one and only child of this node, so it won't have siblings. - args.AutomationProvider(nullptr); - args.Handled(true); - }); - - m_previousSiblingAutomationProviderRequestedToken = m_childSiteLink.PreviousSiblingAutomationProviderRequested( - [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &, - const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) { - // The ContentIsland will always be the one and only child of this node, so it won't have siblings. - args.AutomationProvider(nullptr); - args.Handled(true); - }); + // Determine the automation option to use: + // 1. If explicitly set via builder, use that + // 2. Otherwise, default to FrameworkBased + winrt::Microsoft::UI::Content::ContentAutomationOptions automationOption = + winrt::Microsoft::UI::Content::ContentAutomationOptions::FrameworkBased; + if (m_builder && m_builder->ContentIslandChildSiteAutomationOption().has_value()) { + automationOption = m_builder->ContentIslandChildSiteAutomationOption().value(); } + m_childSiteLink.AutomationOption(automationOption); + if (m_innerAutomationProvider) { m_innerAutomationProvider->SetChildSiteLink(m_childSiteLink); } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp index c9f250f1e06..3e26bdf2355 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp @@ -113,6 +113,16 @@ void ReactCompositionViewComponentBuilder::SetContentIslandComponentViewInitiali }; } +void ReactCompositionViewComponentBuilder::SetContentIslandChildSiteAutomationOption( + winrt::Microsoft::UI::Content::ContentAutomationOptions automationOption) noexcept { + m_contentIslandChildSiteAutomationOption = automationOption; +} + +std::optional +ReactCompositionViewComponentBuilder::ContentIslandChildSiteAutomationOption() const noexcept { + return m_contentIslandChildSiteAutomationOption; +} + void ReactCompositionViewComponentBuilder::SetPortalComponentViewInitializer( const PortalComponentViewInitializer &initializer) noexcept { m_fnCreateView = [initializer]( diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h index c015b33a5fb..116764a2469 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "winrt/Microsoft.ReactNative.Composition.Experimental.h" #include "winrt/Microsoft.ReactNative.Composition.h" #include "winrt/Microsoft.ReactNative.h" @@ -46,6 +47,8 @@ struct ReactCompositionViewComponentBuilder public: // Composition::IReactCompositionViewComponentBuilder void SetViewComponentViewInitializer(const ViewComponentViewInitializer &initializer) noexcept; void SetContentIslandComponentViewInitializer(const ComponentIslandComponentViewInitializer &initializer) noexcept; + void SetContentIslandChildSiteAutomationOption( + winrt::Microsoft::UI::Content::ContentAutomationOptions automationOption) noexcept; void SetPortalComponentViewInitializer(const PortalComponentViewInitializer &initializer) noexcept; void SetCreateVisualHandler(CreateVisualDelegate impl) noexcept; void SetViewFeatures(ComponentViewFeatures viewFeatures) noexcept; @@ -81,6 +84,8 @@ struct ReactCompositionViewComponentBuilder const winrt::Microsoft::ReactNative::Composition::Experimental::IVisualToMountChildrenIntoDelegate & VisualToMountChildrenIntoHandler() const noexcept; const CreateAutomationPeerDelegate &CreateAutomationPeerHandler() const noexcept; + std::optional ContentIslandChildSiteAutomationOption() + const noexcept; private: void InitializeComponentView(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept; @@ -114,6 +119,7 @@ struct ReactCompositionViewComponentBuilder m_visualToMountChildrenIntoHandler; UpdateLayoutMetricsDelegate m_updateLayoutMetricsHandler; bool m_xamlSupport{false}; + std::optional m_contentIslandChildSiteAutomationOption; }; } // namespace winrt::Microsoft::ReactNative::Composition diff --git a/vnext/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl b/vnext/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl index 4ddbdc43b54..bbc7fd90187 100644 --- a/vnext/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +++ b/vnext/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl @@ -21,6 +21,7 @@ namespace Microsoft.ReactNative.Composition [experimental] delegate void ComponentIslandComponentViewInitializer(ContentIslandComponentView view); + [webhosthidden] [experimental] delegate void PortalComponentViewInitializer(PortalComponentView view); @@ -68,6 +69,7 @@ namespace Microsoft.ReactNative.Composition { void SetViewComponentViewInitializer(ViewComponentViewInitializer initializer); void SetContentIslandComponentViewInitializer(ComponentIslandComponentViewInitializer initializer); + void SetContentIslandChildSiteAutomationOption(Microsoft.UI.Content.ContentAutomationOptions automationOption); void SetPortalComponentViewInitializer(PortalComponentViewInitializer initializer); void SetCreateVisualHandler(CreateVisualDelegate impl); void SetViewFeatures(ComponentViewFeatures viewFeatures); From e4cd88b8ee865d423d9a9726dcd3113d83ca217a Mon Sep 17 00:00:00 2001 From: vineethkuttan <66076509+vineethkuttan@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:03:33 +0530 Subject: [PATCH 4/7] remove unwanted changes --- .../Composition/ContentIslandComponentView.cpp | 18 ------------------ .../Composition/ContentIslandComponentView.h | 4 ---- 2 files changed, 22 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp index d73b6df807e..31a4801a4b8 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp @@ -171,24 +171,6 @@ ContentIslandComponentView::~ContentIslandComponentView() noexcept { m_navigationHost.DepartFocusRequested(m_navigationHostDepartFocusRequestedToken); m_navigationHostDepartFocusRequestedToken = {}; } - if (m_childSiteLink) { - if (m_fragmentRootAutomationProviderRequestedToken) { - m_childSiteLink.FragmentRootAutomationProviderRequested(m_fragmentRootAutomationProviderRequestedToken); - m_fragmentRootAutomationProviderRequestedToken = {}; - } - if (m_parentAutomationProviderRequestedToken) { - m_childSiteLink.ParentAutomationProviderRequested(m_parentAutomationProviderRequestedToken); - m_parentAutomationProviderRequestedToken = {}; - } - if (m_nextSiblingAutomationProviderRequestedToken) { - m_childSiteLink.NextSiblingAutomationProviderRequested(m_nextSiblingAutomationProviderRequestedToken); - m_nextSiblingAutomationProviderRequestedToken = {}; - } - if (m_previousSiblingAutomationProviderRequestedToken) { - m_childSiteLink.PreviousSiblingAutomationProviderRequested(m_previousSiblingAutomationProviderRequestedToken); - m_previousSiblingAutomationProviderRequestedToken = {}; - } - } if (m_islandToConnect) { m_islandToConnect.Close(); } diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h index e6f5fe8808a..7c85ee1cc01 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h @@ -70,10 +70,6 @@ struct ContentIslandComponentView : ContentIslandComponentViewT Date: Wed, 4 Feb 2026 15:08:40 +0530 Subject: [PATCH 5/7] Remove Sample XAML Custom Component i) will do it a seperate PR --- packages/sample-app-fabric/App.tsx | 38 ++----------------- packages/sample-app-fabric/package.json | 3 +- .../packages.lock.json | 10 ----- .../windows/SampleAppFabric.sln | 14 ------- .../AutolinkedNativeModules.g.cpp | 6 +-- .../AutolinkedNativeModules.g.targets | 4 -- .../SampleAppFabric/packages.lock.json | 9 ----- packages/sample-custom-component/src/index.ts | 3 -- .../ReactPackageProvider.cpp | 2 - .../SampleCustomComponent.vcxproj | 2 - 10 files changed, 5 insertions(+), 86 deletions(-) diff --git a/packages/sample-app-fabric/App.tsx b/packages/sample-app-fabric/App.tsx index a9abdcfaa67..3ac27a50420 100644 --- a/packages/sample-app-fabric/App.tsx +++ b/packages/sample-app-fabric/App.tsx @@ -5,52 +5,20 @@ * @format */ -import React, {useState} from 'react'; -import { - SafeAreaView, - StatusBar, - useColorScheme, - Text, - View, -} from 'react-native'; +import React from 'react'; +import {SafeAreaView, StatusBar, useColorScheme} from 'react-native'; import {NewAppScreen} from '@react-native/new-app-screen'; -import {PickerXaml} from 'sample-custom-component'; - -const pickerItems = [ - {label: 'JavaScript', value: 'js'}, - {label: 'TypeScript', value: 'ts'}, - {label: 'Python', value: 'py'}, - {label: 'C++', value: 'cpp'}, - {label: 'Rust', value: 'rs'}, -]; function App(): React.JSX.Element { const isDarkMode = useColorScheme() === 'dark'; - const [selectedIndex, setSelectedIndex] = useState(3); // Default to C++ - - const backgroundColor = isDarkMode ? '#1a1a1a' : '#f5f5f5'; - const textColor = isDarkMode ? '#ffffff' : '#000000'; return ( - + - - - Selected: {pickerItems[selectedIndex]?.label} - - { - setSelectedIndex(event.nativeEvent.itemIndex); - }} - /> - ); diff --git a/packages/sample-app-fabric/package.json b/packages/sample-app-fabric/package.json index 0c0607b4731..25c7f5d119e 100644 --- a/packages/sample-app-fabric/package.json +++ b/packages/sample-app-fabric/package.json @@ -18,8 +18,7 @@ "@typescript-eslint/parser": "^7.1.1", "react": "^19.1.1", "react-native": "0.82.0-nightly-20250902-9731e8ebc", - "react-native-windows": "^0.0.0-canary.1031", - "sample-custom-component": "0.0.1" + "react-native-windows": "^0.0.0-canary.1031" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json b/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json index c4416e656d3..d4f48cf99bc 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json +++ b/packages/sample-app-fabric/windows/SampleAppFabric.Package/packages.lock.json @@ -185,16 +185,6 @@ "type": "Project", "dependencies": { "Microsoft.JavaScript.Hermes": "[0.0.0-2512.22001-bc3d0ed7, )", - "Microsoft.ReactNative": "[1.0.0, )", - "Microsoft.VCRTForwarders.140": "[1.0.2-rc, )", - "Microsoft.WindowsAppSDK": "[1.8.251106002, )", - "SampleCustomComponent": "[1.0.0, )", - "boost": "[1.83.0, )" - } - }, - "samplecustomcomponent": { - "type": "Project", - "dependencies": { "Microsoft.ReactNative": "[1.0.0, )", "Microsoft.VCRTForwarders.140": "[1.0.2-rc, )", "Microsoft.WindowsAppSDK": "[1.8.251106002, )", diff --git a/packages/sample-app-fabric/windows/SampleAppFabric.sln b/packages/sample-app-fabric/windows/SampleAppFabric.sln index e562e1f033e..3335ca845cd 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric.sln +++ b/packages/sample-app-fabric/windows/SampleAppFabric.sln @@ -33,8 +33,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\..\..\vnext\Mso\M EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\..\..\vnext\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleCustomComponent", "..\..\..\node_modules\sample-custom-component\windows\SampleCustomComponent\SampleCustomComponent.vcxproj", "{A8DA218C-4CB5-48CB-A9EE-9E6337165D07}" -EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ..\..\..\vnext\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 @@ -152,18 +150,6 @@ Global {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.ActiveCfg = Release|Win32 {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Build.0 = Release|Win32 {14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Deploy.0 = Release|Win32 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x64.ActiveCfg = Debug|x64 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x64.Build.0 = Debug|x64 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x86.ActiveCfg = Debug|Win32 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|x86.Build.0 = Debug|Win32 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Debug|ARM64.Build.0 = Debug|ARM64 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x64.ActiveCfg = Release|x64 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x64.Build.0 = Release|x64 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x86.ActiveCfg = Release|Win32 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|x86.Build.0 = Release|Win32 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|ARM64.ActiveCfg = Release|ARM64 - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07}.Release|ARM64.Build.0 = Release|ARM64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp index b4f9b7ce9c8..5821cfddc55 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp +++ b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.cpp @@ -3,16 +3,12 @@ #include "pch.h" #include "AutolinkedNativeModules.g.h" -// Includes from sample-custom-component -#include - namespace winrt::Microsoft::ReactNative { void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) { - // IReactPackageProviders from sample-custom-component - packageProviders.Append(winrt::SampleCustomComponent::ReactPackageProvider()); + UNREFERENCED_PARAMETER(packageProviders); } } diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets index 35f260ab39e..6a85ec51cd9 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets +++ b/packages/sample-app-fabric/windows/SampleAppFabric/AutolinkedNativeModules.g.targets @@ -2,9 +2,5 @@ - - - {A8DA218C-4CB5-48CB-A9EE-9E6337165D07} - diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json b/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json index 6c00291fc28..8af9287d979 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json +++ b/packages/sample-app-fabric/windows/SampleAppFabric/packages.lock.json @@ -190,15 +190,6 @@ "Folly": "[1.0.0, )", "boost": "[1.83.0, )" } - }, - "samplecustomcomponent": { - "type": "Project", - "dependencies": { - "Microsoft.ReactNative": "[1.0.0, )", - "Microsoft.VCRTForwarders.140": "[1.0.2-rc, )", - "Microsoft.WindowsAppSDK": "[1.8.251106002, )", - "boost": "[1.83.0, )" - } } }, "native,Version=v0.0/win": { diff --git a/packages/sample-custom-component/src/index.ts b/packages/sample-custom-component/src/index.ts index 69288ff72c6..49b2bd07d43 100644 --- a/packages/sample-custom-component/src/index.ts +++ b/packages/sample-custom-component/src/index.ts @@ -7,13 +7,10 @@ import CalendarView from './FabricXamlCalendarViewNativeComponent' import CustomAccessibility from './CustomAccessibilityNativeComponent'; -import PickerXaml from './PickerXamlNativeComponent'; - export { CustomAccessibility, DrawingIsland, MovingLight, MovingLightHandle, CalendarView, - PickerXaml }; \ No newline at end of file diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp b/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp index b9045b1f6e2..29f8c8905e1 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp +++ b/packages/sample-custom-component/windows/SampleCustomComponent/ReactPackageProvider.cpp @@ -11,7 +11,6 @@ #include "CustomAccessibility.h" #include "DrawingIsland.h" #include "MovingLight.h" -#include "PickerXaml.h" using namespace winrt::Microsoft::ReactNative; @@ -25,7 +24,6 @@ void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuil RegisterMovingLightNativeComponent(packageBuilder); RegisterCalendarViewComponentView(packageBuilder); RegisterCustomAccessibilityComponentView(packageBuilder); - RegisterPickerXamlComponentView(packageBuilder); #endif // #ifdef RNW_NEW_ARCH } diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj b/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj index 4cedd6c57e9..2a9dae1e5fe 100644 --- a/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj +++ b/packages/sample-custom-component/windows/SampleCustomComponent/SampleCustomComponent.vcxproj @@ -102,7 +102,6 @@ - DrawingIsland.idl @@ -116,7 +115,6 @@ - Create From 9783b811c249fea0ce274ffa8eace90cf17d21f4 Mon Sep 17 00:00:00 2001 From: vineethkuttan <66076509+vineethkuttan@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:14:04 +0530 Subject: [PATCH 6/7] final changes --- ...-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json | 4 +- .../src/PickerXamlNativeComponent.ts | 43 --- .../SampleCustomComponent/PickerXaml.cpp | 212 --------------- .../SampleCustomComponent/PickerXaml.h | 10 - .../SampleCustomComponent/PickerXaml.g.h | 247 ------------------ 5 files changed, 2 insertions(+), 514 deletions(-) delete mode 100644 packages/sample-custom-component/src/PickerXamlNativeComponent.ts delete mode 100644 packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp delete mode 100644 packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h delete mode 100644 packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h diff --git a/change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json b/change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json index 26087baa119..ee7da3a9438 100644 --- a/change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json +++ b/change/react-native-windows-cdc988c8-8058-4f35-a668-0fc97bf4f9a8.json @@ -1,7 +1,7 @@ { "type": "prerelease", - "comment": "Enable Fragment‑Based Automation for ChildSite Without XAML Islands", + "comment": "Expose AutomationOption Setter for ChildSite", "packageName": "react-native-windows", "email": "66076509+vineethkuttan@users.noreply.github.com", "dependentChangeType": "patch" -} +} \ No newline at end of file diff --git a/packages/sample-custom-component/src/PickerXamlNativeComponent.ts b/packages/sample-custom-component/src/PickerXamlNativeComponent.ts deleted file mode 100644 index 48d1a7ebc21..00000000000 --- a/packages/sample-custom-component/src/PickerXamlNativeComponent.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {codegenNativeComponent} from 'react-native'; -import type { ViewProps } from 'react-native'; -import type { - Int32, - WithDefault, - BubblingEventHandler, -} from 'react-native/Libraries/Types/CodegenTypes'; - -/** - * Represents a single item in the PickerXaml. - */ -export type PickerXamlItem = Readonly<{ - label: string; - value?: string; -}>; - -/** - * Event payload when the picker selection changes. - */ -export type PickerXamlChangeEvent = Readonly<{ - value: string; - itemIndex: Int32; - text: string; -}>; - -export interface PickerXamlProps extends ViewProps { - /** - * Array of picker items to display. - */ - items: ReadonlyArray; - - /** - * The index of the currently selected item. - */ - selectedIndex?: WithDefault; - - /** - * Callback when selection changes. - */ - onPickerSelect?: BubblingEventHandler; -} - -export default codegenNativeComponent('PickerXaml'); diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp deleted file mode 100644 index f76cca0b9c6..00000000000 --- a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.cpp +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "PickerXaml.h" - -#if defined(RNW_NEW_ARCH) - -#include "codegen/react/components/SampleCustomComponent/PickerXaml.g.h" - -#include -#include -#include -#include -#include -#include - -namespace winrt::SampleCustomComponent { - -// State to store the measured size -struct PickerXamlStateData : winrt::implements { - PickerXamlStateData(winrt::Windows::Foundation::Size ds) : desiredSize(ds) {} - winrt::Windows::Foundation::Size desiredSize; -}; - -struct PickerXamlComponentView : winrt::implements, - Codegen::BasePickerXaml { - void InitializeContentIsland( - const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) { - // Create ComboBox for picker functionality - m_comboBox = winrt::Microsoft::UI::Xaml::Controls::ComboBox(); - m_comboBox.HorizontalAlignment(winrt::Microsoft::UI::Xaml::HorizontalAlignment::Stretch); - - // For editable ComboBox, trigger selection change always (not just on commit) - m_comboBox.SelectionChangedTrigger(winrt::Microsoft::UI::Xaml::Controls::ComboBoxSelectionChangedTrigger::Always); - - // Listen for size changes on the comboBox - m_comboBox.SizeChanged([this](auto const & /*sender*/, auto const & /*args*/) { RefreshSize(); }); - - // Listen for selection changes - m_selectionChangedRevoker = m_comboBox.SelectionChanged( - winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); - - // Listen for text submitted (when user presses Enter in editable mode) - m_textSubmittedRevoker = m_comboBox.TextSubmitted( - winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); - - m_island = winrt::Microsoft::UI::Xaml::XamlIsland{}; - m_island.Content(m_comboBox); - islandView.Connect(m_island.ContentIsland()); - m_islandView = winrt::make_weak(islandView); - } - - void UpdateProps( - const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::com_ptr &newProps, - const winrt::com_ptr &oldProps) noexcept override { - BasePickerXaml::UpdateProps(view, newProps, oldProps); - - // Suspend event handlers during programmatic updates to avoid triggering - // change events. Using RAII ensures handlers are always re-attached. - WithEventsSuspended([&]() { - // Always update items on first render, or when size changes - // (We can't compare items directly as the struct lacks operator==) - if (!oldProps || oldProps->items.size() != newProps->items.size() || m_comboBox.Items().Size() == 0) { - m_comboBox.Items().Clear(); - m_items.clear(); - - for (const auto &item : newProps->items) { - // Store item data - m_items.push_back(item); - - // Add item to ComboBox - auto comboBoxItem = winrt::Microsoft::UI::Xaml::Controls::ComboBoxItem(); - comboBoxItem.Content(winrt::box_value(winrt::to_hstring(item.label))); - m_comboBox.Items().Append(comboBoxItem); - } - } - - // Always update selected index on first render or when changed - if (!oldProps || oldProps->selectedIndex != newProps->selectedIndex || - m_comboBox.SelectedIndex() != newProps->selectedIndex) { - const int32_t selectedIndex = newProps->selectedIndex; - if (selectedIndex >= 0 && selectedIndex < static_cast(m_comboBox.Items().Size())) { - m_comboBox.SelectedIndex(selectedIndex); - } else { - m_comboBox.SelectedIndex(-1); - } - } - }); - - RefreshSize(); - } - - void UpdateState( - const winrt::Microsoft::ReactNative::ComponentView & /*view*/, - const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept override { - m_state = newState; - } - - private: - void RefreshSize() { - m_comboBox.Measure(winrt::Windows::Foundation::Size{ - std::numeric_limits::infinity(), std::numeric_limits::infinity()}); - - const auto desiredSize = m_comboBox.DesiredSize(); - - if (m_state) { - const auto currentState = winrt::get_self(m_state.Data()); - if (desiredSize != currentState->desiredSize) { - m_state.UpdateStateWithMutation([desiredSize](winrt::Windows::Foundation::IInspectable /*data*/) { - return winrt::make(desiredSize); - }); - } - } - } - - void EmitPickerSelectEvent() { - if (auto eventEmitter = this->EventEmitter()) { - const int32_t selectedIndex = m_comboBox.SelectedIndex(); - - Codegen::PickerXaml_OnPickerSelect eventArgs; - eventArgs.itemIndex = selectedIndex; - - // Get the selected item value and text if available - if (selectedIndex >= 0 && selectedIndex < static_cast(m_items.size())) { - eventArgs.value = m_items[selectedIndex].value.value_or(""); - eventArgs.text = m_items[selectedIndex].label; - } else { - eventArgs.text = ""; - eventArgs.value = ""; - } - - eventEmitter->onPickerSelect(eventArgs); - } - } - - winrt::weak_ref m_islandView; - winrt::Microsoft::UI::Xaml::XamlIsland m_island{nullptr}; - winrt::Microsoft::UI::Xaml::Controls::ComboBox m_comboBox{nullptr}; - winrt::Microsoft::ReactNative::IComponentState m_state{nullptr}; - std::vector m_items; - winrt::Microsoft::UI::Xaml::Controls::ComboBox::SelectionChanged_revoker m_selectionChangedRevoker; - winrt::Microsoft::UI::Xaml::Controls::ComboBox::TextSubmitted_revoker m_textSubmittedRevoker; - - // RAII helper to temporarily suspend event handlers during programmatic updates. - // This avoids triggering change events when we're setting values from props. - template - void WithEventsSuspended(TAction action) { - m_selectionChangedRevoker.revoke(); - m_textSubmittedRevoker.revoke(); - - action(); - - m_selectionChangedRevoker = m_comboBox.SelectionChanged( - winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); - m_textSubmittedRevoker = m_comboBox.TextSubmitted( - winrt::auto_revoke, [this](const auto & /*sender*/, const auto & /*args*/) { EmitPickerSelectEvent(); }); - } -}; - -} // namespace winrt::SampleCustomComponent - -void RegisterPickerXamlComponentView(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) { - winrt::SampleCustomComponent::Codegen::RegisterPickerXamlNativeComponent< - winrt::SampleCustomComponent::PickerXamlComponentView>( - packageBuilder, - [](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) { - builder.as().XamlSupport(true); - // Use SetContentIslandComponentViewInitializer - builder.SetContentIslandComponentViewInitializer( - [](const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept { - auto userData = winrt::make_self(); - userData->InitializeContentIsland(islandView); - islandView.UserData(*userData); - }); - // Set up initial state with zero size - builder.as().SetInitialStateDataFactory( - [](const winrt::Microsoft::ReactNative::IComponentProps & /*props*/) noexcept { - return winrt::make( - winrt::Windows::Foundation::Size{0, 0}); - }); - - // Register the measure function - reads from state - builder.as().SetMeasureContentHandler( - [](winrt::Microsoft::ReactNative::ShadowNode const &shadowNode, - winrt::Microsoft::ReactNative::LayoutContext const &, - winrt::Microsoft::ReactNative::LayoutConstraints const &) noexcept { - const auto currentState = - winrt::get_self(shadowNode.StateData()); - - if (currentState && currentState->desiredSize.Width > 0) { - // Return the measured size from state - return currentState->desiredSize; - } - - // Return a default size if we don't have a measurement yet - return winrt::Windows::Foundation::Size{100, 32}; - }); - - // Handle state updates - builder.as().SetUpdateStateHandler( - [](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::IComponentState &newState) { - const auto islandView = view.as(); - const auto userData = islandView.UserData().as(); - userData->UpdateState(view, newState); - }); - }); -} - -#endif // defined(RNW_NEW_ARCH) diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h b/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h deleted file mode 100644 index dad8f0a44af..00000000000 --- a/packages/sample-custom-component/windows/SampleCustomComponent/PickerXaml.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#if defined(RNW_NEW_ARCH) - -void RegisterPickerXamlComponentView(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder); - -#endif // defined(RNW_NEW_ARCH) diff --git a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h b/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h deleted file mode 100644 index 5c67b58b5e5..00000000000 --- a/packages/sample-custom-component/windows/SampleCustomComponent/codegen/react/components/SampleCustomComponent/PickerXaml.g.h +++ /dev/null @@ -1,247 +0,0 @@ - -/* - * This file is auto-generated from PickerXamlNativeComponent spec file in flow / TypeScript. - */ -// clang-format off -#pragma once - -#include - -#ifdef RNW_NEW_ARCH -#include - -#include -#include -#endif // #ifdef RNW_NEW_ARCH - -#ifdef RNW_NEW_ARCH - -namespace winrt::SampleCustomComponent::Codegen { - -REACT_STRUCT(PickerXamlSpec_PickerXamlProps_items) -struct PickerXamlSpec_PickerXamlProps_items { - REACT_FIELD(label) - std::string label; - - REACT_FIELD(value) - std::optional value; -}; - -REACT_STRUCT(PickerXamlProps) -struct PickerXamlProps : winrt::implements { - PickerXamlProps(winrt::Microsoft::ReactNative::ViewProps props, const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) - : ViewProps(props) - { - if (cloneFrom) { - auto cloneFromProps = cloneFrom.as(); - items = cloneFromProps->items; - selectedIndex = cloneFromProps->selectedIndex; - } - } - - void SetProp(uint32_t hash, winrt::hstring propName, winrt::Microsoft::ReactNative::IJSValueReader value) noexcept { - winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this); - } - - REACT_FIELD(items) - std::vector items; - - REACT_FIELD(selectedIndex) - int32_t selectedIndex{-1}; - - const winrt::Microsoft::ReactNative::ViewProps ViewProps; -}; - -REACT_STRUCT(PickerXaml_OnPickerSelect) -struct PickerXaml_OnPickerSelect { - REACT_FIELD(value) - std::string value; - - REACT_FIELD(itemIndex) - int32_t itemIndex{}; - - REACT_FIELD(text) - std::string text; -}; - -struct PickerXamlEventEmitter { - PickerXamlEventEmitter(const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) - : m_eventEmitter(eventEmitter) {} - - using OnPickerSelect = PickerXaml_OnPickerSelect; - - void onPickerSelect(OnPickerSelect &value) const { - m_eventEmitter.DispatchEvent(L"pickerSelect", [value](const winrt::Microsoft::ReactNative::IJSValueWriter writer) { - winrt::Microsoft::ReactNative::WriteValue(writer, value); - }); - } - - private: - winrt::Microsoft::ReactNative::EventEmitter m_eventEmitter{nullptr}; -}; - -template -struct BasePickerXaml { - - virtual void UpdateProps( - const winrt::Microsoft::ReactNative::ComponentView &/*view*/, - const winrt::com_ptr &newProps, - const winrt::com_ptr &/*oldProps*/) noexcept { - m_props = newProps; - } - - // UpdateLayoutMetrics will only be called if this method is overridden - virtual void UpdateLayoutMetrics( - const winrt::Microsoft::ReactNative::ComponentView &/*view*/, - const winrt::Microsoft::ReactNative::LayoutMetrics &/*newLayoutMetrics*/, - const winrt::Microsoft::ReactNative::LayoutMetrics &/*oldLayoutMetrics*/) noexcept { - } - - // UpdateState will only be called if this method is overridden - virtual void UpdateState( - const winrt::Microsoft::ReactNative::ComponentView &/*view*/, - const winrt::Microsoft::ReactNative::IComponentState &/*newState*/) noexcept { - } - - virtual void UpdateEventEmitter(const std::shared_ptr &eventEmitter) noexcept { - m_eventEmitter = eventEmitter; - } - - // MountChildComponentView will only be called if this method is overridden - virtual void MountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, - const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &/*args*/) noexcept { - } - - // UnmountChildComponentView will only be called if this method is overridden - virtual void UnmountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, - const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &/*args*/) noexcept { - } - - // Initialize will only be called if this method is overridden - virtual void Initialize(const winrt::Microsoft::ReactNative::ComponentView &/*view*/) noexcept { - } - - // CreateVisual will only be called if this method is overridden - virtual winrt::Microsoft::UI::Composition::Visual CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { - return view.as().Compositor().CreateSpriteVisual(); - } - - // FinalizeUpdate will only be called if this method is overridden - virtual void FinalizeUpdate(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, - winrt::Microsoft::ReactNative::ComponentViewUpdateMask /*mask*/) noexcept { - } - - // CreateAutomationPeer will only be called if this method is overridden - virtual winrt::Windows::Foundation::IInspectable CreateAutomationPeer(const winrt::Microsoft::ReactNative::ComponentView & /*view*/, - const winrt::Microsoft::ReactNative::CreateAutomationPeerArgs& /*args*/) noexcept { - return nullptr; - } - - - - const std::shared_ptr& EventEmitter() const { return m_eventEmitter; } - const winrt::com_ptr& Props() const { return m_props; } - -private: - winrt::com_ptr m_props; - std::shared_ptr m_eventEmitter; -}; - -template -void RegisterPickerXamlNativeComponent( - winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder, - std::function builderCallback) noexcept { - packageBuilder.as().AddViewComponent( - L"PickerXaml", [builderCallback](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept { - auto compBuilder = builder.as(); - - builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props, - const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom) noexcept { - return winrt::make(props, cloneFrom); - }); - - builder.SetUpdatePropsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::IComponentProps &newProps, - const winrt::Microsoft::ReactNative::IComponentProps &oldProps) noexcept { - auto userData = view.UserData().as(); - userData->UpdateProps(view, newProps ? newProps.as() : nullptr, oldProps ? oldProps.as() : nullptr); - }); - - compBuilder.SetUpdateLayoutMetricsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::LayoutMetrics &newLayoutMetrics, - const winrt::Microsoft::ReactNative::LayoutMetrics &oldLayoutMetrics) noexcept { - auto userData = view.UserData().as(); - userData->UpdateLayoutMetrics(view, newLayoutMetrics, oldLayoutMetrics); - }); - - builder.SetUpdateEventEmitterHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) noexcept { - auto userData = view.UserData().as(); - userData->UpdateEventEmitter(std::make_shared(eventEmitter)); - }); - - if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::FinalizeUpdate != &BasePickerXaml::FinalizeUpdate) { - builder.SetFinalizeUpdateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - winrt::Microsoft::ReactNative::ComponentViewUpdateMask mask) noexcept { - auto userData = view.UserData().as(); - userData->FinalizeUpdate(view, mask); - }); - } - - if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::UpdateState != &BasePickerXaml::UpdateState) { - builder.SetUpdateStateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept { - auto userData = view.UserData().as(); - userData->UpdateState(view, newState); - }); - } - - if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::MountChildComponentView != &BasePickerXaml::MountChildComponentView) { - builder.SetMountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept { - auto userData = view.UserData().as(); - return userData->MountChildComponentView(view, args); - }); - } - - if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::UnmountChildComponentView != &BasePickerXaml::UnmountChildComponentView) { - builder.SetUnmountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept { - auto userData = view.UserData().as(); - return userData->UnmountChildComponentView(view, args); - }); - } - - if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::CreateAutomationPeer != &BasePickerXaml::CreateAutomationPeer) { - builder.SetCreateAutomationPeerHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, - const winrt::Microsoft::ReactNative::CreateAutomationPeerArgs& args) noexcept { - auto userData = view.UserData().as(); - return userData->CreateAutomationPeer(view, args); - }); - } - - compBuilder.SetViewComponentViewInitializer([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { - auto userData = winrt::make_self(); - if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::Initialize != &BasePickerXaml::Initialize) { - userData->Initialize(view); - } - view.UserData(*userData); - }); - - if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::CreateVisual != &BasePickerXaml::CreateVisual) { - compBuilder.SetCreateVisualHandler([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { - auto userData = view.UserData().as(); - return userData->CreateVisual(view); - }); - } - - // Allow app to further customize the builder - if (builderCallback) { - builderCallback(compBuilder); - } - }); -} - -} // namespace winrt::SampleCustomComponent::Codegen - -#endif // #ifdef RNW_NEW_ARCH From f682319a2a708275ab8586be5f4602f111f5f4a4 Mon Sep 17 00:00:00 2001 From: vineethkuttan <66076509+vineethkuttan@users.noreply.github.com> Date: Mon, 9 Feb 2026 15:59:20 +0530 Subject: [PATCH 7/7] format changes --- .../Fabric/Composition/ContentIslandComponentView.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp index 31a4801a4b8..b19ecbab1e2 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp @@ -215,14 +215,11 @@ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept { // Determine the automation option to use: // 1. If explicitly set via builder, use that // 2. Otherwise, default to FrameworkBased - winrt::Microsoft::UI::Content::ContentAutomationOptions automationOption = - winrt::Microsoft::UI::Content::ContentAutomationOptions::FrameworkBased; - if (m_builder && m_builder->ContentIslandChildSiteAutomationOption().has_value()) { - automationOption = m_builder->ContentIslandChildSiteAutomationOption().value(); + if (m_builder) { + m_childSiteLink.AutomationOption(m_builder->ContentIslandChildSiteAutomationOption().value_or( + winrt::Microsoft::UI::Content::ContentAutomationOptions::FrameworkBased)); } - m_childSiteLink.AutomationOption(automationOption); - if (m_innerAutomationProvider) { m_innerAutomationProvider->SetChildSiteLink(m_childSiteLink); }