diff --git a/.changeset/whole-keys-cry.md b/.changeset/whole-keys-cry.md
new file mode 100644
index 0000000000..c0705c8070
--- /dev/null
+++ b/.changeset/whole-keys-cry.md
@@ -0,0 +1,6 @@
+---
+"@fluentui-react-native/components": patch
+"@fluentui-react-native/callout": patch
+---
+
+Very initial framework for RNW fabric callout implementation
diff --git a/packages/agentic-components/package.json b/packages/agentic-components/package.json
index d2286f6be3..618f15c3e1 100644
--- a/packages/agentic-components/package.json
+++ b/packages/agentic-components/package.json
@@ -30,6 +30,7 @@
"update-snapshots": "fluentui-scripts jest -u"
},
"dependencies": {
+ "@fluentui-react-native/callout": "workspace:*",
"@fluentui-react-native/design": "workspace:*",
"@fluentui-react-native/framework-base": "workspace:*"
},
@@ -50,6 +51,7 @@
"test-renderer": "catalog:0.81"
},
"peerDependencies": {
+ "@office-iss/react-native-win32": "^0.74.0 || ^0.81.0",
"@types/react": "~18.2.0 || ~19.0.0 || ~19.1.4",
"react": "18.2.0 || 19.0.0 || 19.1.4",
"react-native": "^0.73.0 || ^0.74.0 || ^0.78.0 || ^0.81.6",
@@ -58,6 +60,9 @@
"react-native-windows": "^0.73.0 || ^0.74.0 || ^0.78.0 || ^0.81.0"
},
"peerDependenciesMeta": {
+ "@office-iss/react-native-win32": {
+ "optional": true
+ },
"@types/react": {
"optional": true
},
@@ -69,7 +74,12 @@
}
},
"furn": {
- "jestPlatform": "macos"
+ "jestPlatform": "macos",
+ "knip": {
+ "ignoreDependencies": [
+ "@fluentui-react-native/callout"
+ ]
+ }
},
"rnx-kit": {
"kitType": "library",
diff --git a/packages/agentic-components/storybook/Directory.Build.props b/packages/agentic-components/storybook/Directory.Build.props
new file mode 100644
index 0000000000..037a06abb3
--- /dev/null
+++ b/packages/agentic-components/storybook/Directory.Build.props
@@ -0,0 +1,11 @@
+
+
+
+
+
+ yarn exec rnc-cli autolink-windows
+ false
+ yarn exec rnc-cli codegen-windows
+
+
+
\ No newline at end of file
diff --git a/packages/agentic-components/tsconfig.json b/packages/agentic-components/tsconfig.json
index 14a3238c30..6c3e991296 100644
--- a/packages/agentic-components/tsconfig.json
+++ b/packages/agentic-components/tsconfig.json
@@ -9,6 +9,9 @@
"include": ["src"],
"exclude": ["**/*.stories.ts", "**/*.stories.tsx"],
"references": [
+ {
+ "path": "../components/Callout/tsconfig.json"
+ },
{
"path": "../agentic-design/tsconfig.json"
},
diff --git a/packages/components/Callout/react-native.config.cjs b/packages/components/Callout/react-native.config.cjs
new file mode 100644
index 0000000000..5046885ab4
--- /dev/null
+++ b/packages/components/Callout/react-native.config.cjs
@@ -0,0 +1,15 @@
+module.exports = {
+ dependency: {
+ platforms: {
+ windows: {
+ sourceDir: 'windows',
+ projects: [
+ {
+ projectFile: 'Callout/Callout.vcxproj',
+ directDependency: true,
+ },
+ ],
+ },
+ },
+ },
+};
diff --git a/packages/components/Callout/src/Callout.stories.tsx b/packages/components/Callout/src/Callout.stories.tsx
index cec96831ad..87ddaf51e9 100644
--- a/packages/components/Callout/src/Callout.stories.tsx
+++ b/packages/components/Callout/src/Callout.stories.tsx
@@ -66,7 +66,7 @@ const CalloutExample = ({ defaultVisible = false, onDismiss, onShow, showWindowC
{visible && (
-
+
Fabric Callout
This content is hosted in a separate native macOS window.
{showWindowCommands && (
@@ -127,7 +127,7 @@ const PlacementExample = (props: CalloutProps) => {
onDismiss={() => setSelectedIndex(undefined)}
target={targetRefs[selectedIndex]}
>
-
+
{placements[selectedIndex].label}
Click outside the native window to dismiss it.
diff --git a/packages/components/Callout/windows/Callout/Callout.cpp b/packages/components/Callout/windows/Callout/Callout.cpp
new file mode 100644
index 0000000000..367299f186
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/Callout.cpp
@@ -0,0 +1,558 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+
+#include "Callout.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace winrt::FluentUI::Callout {
+
+enum class DirectionalHint {
+ LeftTopEdge,
+ LeftCenter,
+ LeftBottomEdge,
+ TopLeftEdge,
+ TopAutoEdge,
+ TopCenter,
+ TopRightEdge,
+ RightTopEdge,
+ RightCenter,
+ RightBottomEdge,
+ BottomLeftEdge,
+ BottomAutoEdge,
+ BottomCenter,
+ BottomRightEdge
+};
+
+DirectionalHint parseDirectionHint(std::string &directionHint) {
+ if (directionHint == "leftTopEdge") {
+ return DirectionalHint::LeftTopEdge;
+ } else if (directionHint == "leftCenter") {
+ return DirectionalHint::LeftCenter;
+ } else if (directionHint == "leftBottomEdge") {
+ return DirectionalHint::LeftBottomEdge;
+ } else if (directionHint == "topLeftEdge") {
+ return DirectionalHint::TopLeftEdge;
+ } else if (directionHint == "topAutoEdge") {
+ return DirectionalHint::TopAutoEdge;
+ } else if (directionHint == "topCenter") {
+ return DirectionalHint::TopCenter;
+ } else if (directionHint == "topRightEdge") {
+ return DirectionalHint::TopRightEdge;
+ } else if (directionHint == "rightTopEdge") {
+ return DirectionalHint::RightTopEdge;
+ } else if (directionHint == "rightCenter") {
+ return DirectionalHint::RightCenter;
+ } else if (directionHint == "rightBottomEdge") {
+ return DirectionalHint::RightBottomEdge;
+ } else if (directionHint == "bottomLeftEdge") {
+ return DirectionalHint::BottomLeftEdge;
+ } else if (directionHint == "bottomAutoEdge") {
+ return DirectionalHint::BottomAutoEdge;
+ } else if (directionHint == "bottomCenter") {
+ return DirectionalHint::BottomCenter;
+ } else if (directionHint == "bottomRightEdge") {
+ return DirectionalHint::BottomRightEdge;
+ }
+
+ return DirectionalHint::LeftTopEdge;
+}
+
+struct CalloutComponentView
+ : public winrt::implements,
+ Codegen::BaseRCTCallout {
+ ~CalloutComponentView() {
+ if (m_popup && !m_popup.IsClosed()) {
+ /*
+ // Unregister closing event handler
+ if (m_appWindowClosingToken)
+ {
+ m_rnWindow.AppWindow().Closing(m_appWindowClosingToken);
+ m_appWindowClosingToken = {};
+ }
+ */
+
+ // Hide popup
+ if (m_popup.IsVisible()) {
+ m_popup.Hide();
+ }
+
+ if (m_rnWindow.AppWindow()) {
+ m_rnWindow.Close();
+ m_rnWindow = nullptr;
+ }
+
+ // Close bridge
+ m_popup.Close();
+ m_popup = nullptr;
+ }
+ }
+
+ void UpdateProps(const winrt::Microsoft::ReactNative::ComponentView &view,
+ const winrt::com_ptr &newProps,
+ const winrt::com_ptr
+ &oldProps) noexcept override {
+ if (!oldProps || newProps->directionalHint != oldProps->directionalHint) {
+ m_directionalHint =
+ newProps->directionalHint
+ ? parseDirectionHint(newProps->directionalHint.value())
+ : DirectionalHint::LeftTopEdge;
+
+ auto portal = view.as<
+ winrt::Microsoft::ReactNative::Composition::PortalComponentView>();
+ if (portal.ContentRoot().Children().Size() != 0) {
+ AdjustWindowSize(
+ portal.ContentRoot().Children().GetAt(0).LayoutMetrics());
+ }
+ }
+
+ __super::UpdateProps(view, newProps, oldProps);
+ }
+
+ void InitializePortalViewComponent(
+ const winrt::Microsoft::ReactNative::Composition::PortalComponentView
+ &portalComponentView) noexcept {
+ m_reactContext = portalComponentView.ReactContext();
+
+ portalComponentView.Mounted([](const auto & /*sender*/, const auto &view) {
+ view.UserData().as()->OnMounted(view);
+ });
+ portalComponentView.Unmounted(
+ [](const auto & /*sender*/, const auto &view) {
+ view.UserData().as()->OnUnmounted(view);
+ });
+ }
+
+ void HandleFocusWindowCommand() noexcept override {
+ // nyi
+ }
+
+ // You must provide an implementation of this method to handle the
+ // "blurWindow" command
+ void HandleBlurWindowCommand() noexcept override {
+ // nyi
+ }
+
+ void MountChildComponentView(
+ const winrt::Microsoft::ReactNative::ComponentView & /*view*/,
+ const winrt::Microsoft::ReactNative::MountChildComponentViewArgs
+ &args) noexcept override {
+ AdjustWindowSize(args.Child().LayoutMetrics());
+ // TODO These asserts are currently hit if the root view of the callout is collapsed.
+ // Need to handle this case
+ // assert(args.Index() == 0);
+ // assert(!m_childLayoutMetricsToken);
+ m_childLayoutMetricsToken = args.Child().LayoutMetricsChanged(
+ [wkThis = get_weak()](
+ auto &/*sender*/,
+ const winrt::Microsoft::ReactNative::LayoutMetricsChangedArgs
+ &layoutMetricsChangedArgs) {
+ if (auto strongThis = wkThis.get()) {
+ strongThis->AdjustWindowSize(
+ layoutMetricsChangedArgs.NewLayoutMetrics());
+ }
+ });
+ }
+
+ void UnmountChildComponentView(
+ const winrt::Microsoft::ReactNative::ComponentView & /*view*/,
+ const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs
+ &args) noexcept override {
+ // TODO These asserts are currently hit if the root view of the callout is collapsed.
+ // Need to handle this case
+ // assert(args.Index() == 0);
+ // assert(m_childLayoutMetricsToken);
+ args.Child().LayoutMetricsChanged(m_childLayoutMetricsToken);
+ // m_childLayoutMetricsToken.value = 0;
+ }
+
+private:
+ void
+ OnMounted(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
+ assert(!m_mounted);
+ m_mounted = true;
+
+ CreatePopup(view);
+
+ m_showQueued = true;
+
+ m_reactContext.UIDispatcher().Post(
+ [wkThis = get_weak(), wkView = winrt::weak_ref(view)]() {
+ if (auto strongThis = wkThis.get()) {
+ strongThis->m_showQueued = false;
+
+ if (!strongThis->m_mounted) {
+ return;
+ }
+ if (auto v = wkView.get()) {
+ strongThis->Show();
+ }
+ }
+ });
+ }
+
+ void OnUnmounted(
+ const winrt::Microsoft::ReactNative::ComponentView & /*view*/) noexcept {
+ assert(m_mounted);
+ m_mounted = false;
+ }
+
+ void OnLightDismissDismissed(
+ const winrt::Microsoft::UI::Input::InputLightDismissAction &,
+ const winrt::Microsoft::UI::Input::InputLightDismissEventArgs &) {
+ HidePopup();
+ }
+
+ void HidePopup() {
+ if (!m_popup)
+ return;
+ m_popup.Hide();
+ if (auto eventEmitter = EventEmitter())
+ eventEmitter->onDismiss({});
+ }
+
+ void CreatePopup(
+ const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
+ if (m_popup)
+ return;
+
+ auto portal = view.as<
+ winrt::Microsoft::ReactNative::Composition::PortalComponentView>();
+ m_popup = winrt::Microsoft::UI::Content::DesktopPopupSiteBridge::Create(
+ portal.Parent()
+ .as()
+ .Root()
+ .ReactNativeIsland()
+ .Island());
+
+ assert(!m_rnWindow);
+ m_rnWindow = winrt::Microsoft::ReactNative::ReactNativeWindow::
+ CreateFromContentSiteBridgeAndIsland(
+ m_popup,
+ winrt::Microsoft::ReactNative::ReactNativeIsland::CreatePortal(
+ portal));
+ m_rnWindow.ResizePolicy(
+ winrt::Microsoft::ReactNative::ContentSizePolicy::None);
+
+ auto inputLightDismissAction =
+ winrt::Microsoft::UI::Input::InputLightDismissAction::GetForWindowId(
+ m_rnWindow.AppWindow().Id());
+ inputLightDismissAction.Dismissed(
+ {get_weak(), &CalloutComponentView::OnLightDismissDismissed});
+
+ if (portal.ContentRoot().Children().Size()) {
+ AdjustWindowSize(
+ portal.ContentRoot().Children().GetAt(0).LayoutMetrics());
+ }
+ }
+
+ void RegisterLightDismissAction() noexcept {}
+
+ void Show() noexcept {
+ m_popup.Show();
+
+ winrt::Microsoft::UI::Input::InputFocusController::GetForIsland(
+ m_rnWindow.ReactNativeIsland().Island())
+ .TrySetFocus();
+ m_rnWindow.ReactNativeIsland().NavigateFocus(
+ winrt::Microsoft::ReactNative::FocusNavigationRequest::
+ FocusNavigationRequest(
+ winrt::Microsoft::ReactNative::FocusNavigationReason::First));
+
+ if (auto eventEmitter = EventEmitter()) {
+ eventEmitter->onShow({});
+ }
+ }
+
+ void AdjustWindowSize(const winrt::Microsoft::ReactNative::LayoutMetrics
+ &layoutMetrics) noexcept {
+ if (!m_rnWindow) {
+ return;
+ }
+
+ if (layoutMetrics.Frame.Width == 0 && layoutMetrics.Frame.Height == 0) {
+ return;
+ }
+
+ // Calculate physical pixels from DIPs
+ int32_t clientWidthPx = static_cast(
+ layoutMetrics.Frame.Width * layoutMetrics.PointScaleFactor);
+ int32_t clientHeightPx = static_cast(
+ layoutMetrics.Frame.Height * layoutMetrics.PointScaleFactor);
+
+ // Size the client area directly
+ m_rnWindow.AppWindow().ResizeClient({clientWidthPx, clientHeightPx});
+
+ // Target can be either a view tag of a view to anchor to, or a string for
+ // an anchor id
+ if (Props()->target.Type() ==
+ winrt::Microsoft::ReactNative::JSValueType::Int64) {
+ auto targetView = winrt::Microsoft::ReactNative::Composition::
+ CompositionUIService::ComponentFromReactTag(
+ m_reactContext.Handle(), Props()->target.AsInt64());
+ auto targetPos = ViewToScreenOffset(targetView);
+
+ POINT anchorPoint{targetPos.X, targetPos.Y};
+ SIZE windowSize{clientWidthPx, clientHeightPx};
+
+ RECT excludeRect{targetPos.X, targetPos.Y,
+ targetPos.X + targetView.LayoutMetrics().Frame.Width,
+ targetPos.Y + targetView.LayoutMetrics().Frame.Height};
+
+ UINT flags = 0;
+
+ if (m_directionalHint == DirectionalHint::LeftTopEdge) {
+ flags = TPM_LEFTALIGN | TPM_TOPALIGN | TPM_HORIZONTAL;
+ } else if (m_directionalHint == DirectionalHint::LeftCenter) {
+ flags = TPM_LEFTALIGN | TPM_VCENTERALIGN | TPM_HORIZONTAL;
+ } else if (m_directionalHint == DirectionalHint::LeftBottomEdge) {
+ flags = TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_HORIZONTAL;
+ } else if (m_directionalHint == DirectionalHint::TopLeftEdge) {
+ flags = TPM_LEFTALIGN | TPM_TOPALIGN | TPM_VERTICAL;
+ } else if (m_directionalHint == DirectionalHint::TopAutoEdge) {
+ flags = TPM_LEFTALIGN | TPM_TOPALIGN | TPM_VERTICAL;
+ } else if (m_directionalHint == DirectionalHint::TopCenter) {
+ flags = TPM_CENTERALIGN | TPM_TOPALIGN | TPM_VERTICAL;
+ } else if (m_directionalHint == DirectionalHint::TopRightEdge) {
+ flags = TPM_RIGHTALIGN | TPM_TOPALIGN | TPM_VERTICAL;
+ } else if (m_directionalHint == DirectionalHint::RightTopEdge) {
+ flags = TPM_RIGHTALIGN | TPM_TOPALIGN | TPM_HORIZONTAL;
+ } else if (m_directionalHint == DirectionalHint::RightCenter) {
+ flags = TPM_RIGHTALIGN | TPM_VCENTERALIGN | TPM_HORIZONTAL;
+ } else if (m_directionalHint == DirectionalHint::RightBottomEdge) {
+ flags = TPM_RIGHTALIGN | TPM_BOTTOMALIGN | TPM_HORIZONTAL;
+ } else if (m_directionalHint == DirectionalHint::BottomLeftEdge) {
+ flags = TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_VERTICAL;
+ } else if (m_directionalHint == DirectionalHint::BottomAutoEdge) {
+ flags = TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_VERTICAL;
+ } else if (m_directionalHint == DirectionalHint::BottomCenter) {
+ flags = TPM_CENTERALIGN | TPM_BOTTOMALIGN | TPM_VERTICAL;
+ } else if (m_directionalHint == DirectionalHint::BottomRightEdge) {
+ flags = TPM_RIGHTALIGN | TPM_BOTTOMALIGN | TPM_VERTICAL;
+ }
+
+ flags |= TPM_WORKAREA;
+
+ RECT finalPos;
+
+ CalculatePopupWindowPosition(&anchorPoint, &windowSize, flags,
+ &excludeRect, &finalPos);
+
+ m_rnWindow.AppWindow().Move({finalPos.left, finalPos.top});
+ } else {
+ // TODO target named anchors
+ m_rnWindow.AppWindow().Move({0, 0});
+ }
+ };
+
+ winrt::Windows::Graphics::PointInt32
+ ViewToScreenOffset(const winrt::Microsoft::ReactNative::ComponentView &view) {
+ winrt::Windows::Foundation::Point pt{
+ (view.LayoutMetrics().Frame.X) * view.LayoutMetrics().PointScaleFactor,
+ (view.LayoutMetrics().Frame.Y) * view.LayoutMetrics().PointScaleFactor};
+
+ for (auto p = view.Parent(); p; p = p.Parent()) {
+ pt.X += p.LayoutMetrics().Frame.X * p.LayoutMetrics().PointScaleFactor;
+ pt.Y += p.LayoutMetrics().Frame.Y * p.LayoutMetrics().PointScaleFactor;
+ }
+
+ auto root =
+ view.as()
+ .Root();
+ auto cc = root.ReactNativeIsland().Island().CoordinateConverter();
+ return cc.ConvertLocalToScreen(pt);
+ }
+
+ DirectionalHint m_directionalHint{DirectionalHint::LeftTopEdge};
+ bool m_showQueued{false};
+ bool m_mounted{false};
+ winrt::event_token m_childLayoutMetricsToken;
+ winrt::Microsoft::UI::Content::DesktopPopupSiteBridge m_popup{nullptr};
+ winrt::Microsoft::ReactNative::ReactNativeWindow m_rnWindow{nullptr};
+ winrt::Microsoft::ReactNative::ReactContext m_reactContext{nullptr};
+};
+
+} // namespace winrt::FluentUI::Callout
+
+namespace winrt::FluentUI::Callout::Codegen {
+
+// Copy/Paste of codegen'd RegisterRCTCalloutNativeComponent, but uses "Callout"
+// to register the component instead of RCTCallout RN has special cased RCT* to
+// be just *. But the codegen doesn't know that. We should probably eventually
+// move away from using RCT prefixes everywhere too.
+template
+void RegisterRCTCalloutNativeComponent2(
+ winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder,
+ std::function
+ builderCallback) noexcept {
+ packageBuilder.as()
+ .AddViewComponent(L"Callout", [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 !=
+ &BaseRCTCallout::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 !=
+ &BaseRCTCallout<
+ TUserData>::
+ 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);
+ });
+ }
+
+ builder.SetCustomCommandHandler(
+ [](const winrt::Microsoft::ReactNative::ComponentView &view,
+ const winrt::Microsoft::ReactNative::HandleCommandArgs
+ &args) noexcept {
+ auto userData = view.UserData().as();
+ userData->HandleCommand(view, args);
+ });
+
+ if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (
+ &TUserData::MountChildComponentView !=
+ &BaseRCTCallout::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 !=
+ &BaseRCTCallout::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 !=
+ &BaseRCTCallout::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 !=
+ &BaseRCTCallout::Initialize) {
+ userData->Initialize(view);
+ }
+ view.UserData(*userData);
+ });
+
+ if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (
+ &TUserData::CreateVisual !=
+ &BaseRCTCallout::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::FluentUI::Callout::Codegen
+
+void RegisterCalloutComponentView(
+ winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) {
+ winrt::FluentUI::Callout::Codegen::RegisterRCTCalloutNativeComponent2<
+ winrt::FluentUI::Callout::CalloutComponentView>(
+ packageBuilder, [](const winrt::Microsoft::ReactNative::Composition::
+ IReactCompositionViewComponentBuilder &builder) {
+ builder.SetPortalComponentViewInitializer(
+ [](const winrt::Microsoft::ReactNative::Composition::
+ PortalComponentView &portalComponentView) noexcept {
+ auto userData = winrt::make_self<
+ winrt::FluentUI::Callout::CalloutComponentView>();
+ userData->InitializePortalViewComponent(portalComponentView);
+ portalComponentView.UserData(*userData);
+ });
+ });
+};
\ No newline at end of file
diff --git a/packages/components/Callout/windows/Callout/Callout.def b/packages/components/Callout/windows/Callout/Callout.def
new file mode 100644
index 0000000000..24e7c1235c
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/Callout.def
@@ -0,0 +1,3 @@
+EXPORTS
+DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
+DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
diff --git a/packages/components/Callout/windows/Callout/Callout.h b/packages/components/Callout/windows/Callout/Callout.h
new file mode 100644
index 0000000000..b1f8e8d61f
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/Callout.h
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#pragma once
+
+#include "pch.h"
+
+#include "NativeModules.h"
+
+#include "../codegen/react/components/FRNCalloutSpec/RCTCallout.g.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace winrt::FluentUI::Callout {
+
+} // namespace winrt::FluentUI::Callout
+
+void RegisterCalloutComponentView(
+ winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder);
diff --git a/packages/components/Callout/windows/Callout/Callout.vcxproj b/packages/components/Callout/windows/Callout/Callout.vcxproj
new file mode 100644
index 0000000000..3799c9cb2a
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/Callout.vcxproj
@@ -0,0 +1,151 @@
+
+
+
+
+
+ true
+ true
+ true
+ {FE36524A-FE8C-456A-9368-08FB1EB16CF8}
+ FluentUICallout
+ FluentUI.Callout
+ en-US
+ 16.0
+ false
+ Windows Store
+ 10.0
+ 10.0.19041.0
+ 10.0.17763.0
+
+
+ $([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\
+
+
+
+
+
+ Debug
+ ARM
+
+
+ Debug
+ ARM64
+
+
+ Debug
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ ARM
+
+
+ Release
+ ARM64
+
+
+ Release
+ Win32
+
+
+ Release
+ x64
+
+
+
+ DynamicLibrary
+ Unicode
+ false
+
+
+ true
+ true
+
+
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Use
+ pch.h
+ $(IntDir)pch.pch
+ Level4
+ %(AdditionalOptions) /bigobj
+ 4453;28204
+ _WINRT_DLL;%(PreprocessorDefinitions)
+ $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)
+
+
+ User32.lib;%(AdditionalDependencies)
+ Console
+ true
+ Callout.def
+
+
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+
+
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+
+
+
+
+ false
+
+
+
+
+
+ ReactPackageProvider.idl
+
+
+
+
+
+
+ Create
+
+
+ ReactPackageProvider.idl
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references targets in your node_modules\react-native-windows folder that are missing. The missing file is {0}.
+
+
+
+
+
diff --git a/packages/components/Callout/windows/Callout/ReactPackageProvider.cpp b/packages/components/Callout/windows/Callout/ReactPackageProvider.cpp
new file mode 100644
index 0000000000..bac1881140
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/ReactPackageProvider.cpp
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+
+#include "ReactPackageProvider.h"
+#if __has_include("ReactPackageProvider.g.cpp")
+#include "ReactPackageProvider.g.cpp"
+#endif
+
+#include "Callout.h"
+
+using namespace winrt::Microsoft::ReactNative;
+
+namespace winrt::FluentUI::Callout::implementation {
+
+void ReactPackageProvider::CreatePackage(
+ IReactPackageBuilder const &packageBuilder) noexcept {
+ AddAttributedModules(packageBuilder, true);
+
+#ifdef RNW_NEW_ARCH
+ RegisterCalloutComponentView(packageBuilder);
+#endif // #ifdef RNW_NEW_ARCH
+}
+
+} // namespace winrt::FluentUI::Callout::implementation
diff --git a/packages/components/Callout/windows/Callout/ReactPackageProvider.h b/packages/components/Callout/windows/Callout/ReactPackageProvider.h
new file mode 100644
index 0000000000..ee4a609103
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/ReactPackageProvider.h
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#pragma once
+
+#include "ReactPackageProvider.g.h"
+
+using namespace winrt::Microsoft::ReactNative;
+
+namespace winrt::FluentUI::Callout::implementation {
+
+struct ReactPackageProvider : ReactPackageProviderT {
+ ReactPackageProvider() = default;
+
+ void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept;
+};
+
+} // namespace winrt::FluentUI::Callout::implementation
+
+namespace winrt::FluentUI::Callout::factory_implementation {
+
+struct ReactPackageProvider
+ : ReactPackageProviderT {};
+
+} // namespace winrt::FluentUI::Callout::factory_implementation
diff --git a/packages/components/Callout/windows/Callout/ReactPackageProvider.idl b/packages/components/Callout/windows/Callout/ReactPackageProvider.idl
new file mode 100644
index 0000000000..cd726b6e61
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/ReactPackageProvider.idl
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace FluentUI.Callout
+{
+ [webhosthidden]
+ [default_interface]
+ runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider
+ {
+ ReactPackageProvider();
+ };
+}
diff --git a/packages/components/Callout/windows/Callout/codegen/.clang-format b/packages/components/Callout/windows/Callout/codegen/.clang-format
new file mode 100644
index 0000000000..9d159247d5
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/codegen/.clang-format
@@ -0,0 +1,2 @@
+DisableFormat: true
+SortIncludes: false
diff --git a/packages/components/Callout/windows/Callout/codegen/react/components/FRNCalloutSpec/RCTCallout.g.h b/packages/components/Callout/windows/Callout/codegen/react/components/FRNCalloutSpec/RCTCallout.g.h
new file mode 100644
index 0000000000..be4153ee8f
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/codegen/react/components/FRNCalloutSpec/RCTCallout.g.h
@@ -0,0 +1,359 @@
+
+/*
+ * This file is auto-generated from RCTCalloutNativeComponent 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::FluentUI::Callout::Codegen {
+
+REACT_STRUCT(RCTCalloutSpec_RCTCalloutProps_anchorRect)
+struct RCTCalloutSpec_RCTCalloutProps_anchorRect {
+ REACT_FIELD(screenX)
+ double screenX{};
+
+ REACT_FIELD(screenY)
+ double screenY{};
+
+ REACT_FIELD(width)
+ double width{};
+
+ REACT_FIELD(height)
+ double height{};
+};
+
+REACT_STRUCT(RCTCalloutProps)
+struct RCTCalloutProps : winrt::implements {
+ RCTCalloutProps(winrt::Microsoft::ReactNative::ViewProps props, const winrt::Microsoft::ReactNative::IComponentProps& cloneFrom)
+ : ViewProps(props)
+ {
+ if (cloneFrom) {
+ auto cloneFromProps = cloneFrom.as();
+ accessibilityLabel = cloneFromProps->accessibilityLabel;
+ accessibilityOnShowAnnouncement = cloneFromProps->accessibilityOnShowAnnouncement;
+ anchorRect = cloneFromProps->anchorRect;
+ dismissBehaviors = cloneFromProps->dismissBehaviors;
+ doNotTakePointerCapture = cloneFromProps->doNotTakePointerCapture;
+ focusable = cloneFromProps->focusable;
+ isBeakVisible = cloneFromProps->isBeakVisible;
+ maxHeight = cloneFromProps->maxHeight;
+ maxWidth = cloneFromProps->maxWidth;
+ setInitialFocus = cloneFromProps->setInitialFocus;
+ testID = cloneFromProps->testID;
+ directionalHint = cloneFromProps->directionalHint;
+ target = cloneFromProps->target.Copy();
+ onRestoreFocus = cloneFromProps->onRestoreFocus;
+ onDismiss = cloneFromProps->onDismiss;
+ onShow = cloneFromProps->onShow;
+ }
+ }
+
+ void SetProp(uint32_t hash, winrt::hstring propName, winrt::Microsoft::ReactNative::IJSValueReader value) noexcept {
+ winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this);
+ }
+
+ REACT_FIELD(accessibilityLabel)
+ std::optional accessibilityLabel;
+
+ REACT_FIELD(accessibilityOnShowAnnouncement)
+ std::optional accessibilityOnShowAnnouncement;
+
+ REACT_FIELD(anchorRect)
+ std::optional anchorRect;
+
+ REACT_FIELD(dismissBehaviors)
+ std::optional> dismissBehaviors;
+
+ REACT_FIELD(doNotTakePointerCapture)
+ std::optional doNotTakePointerCapture{};
+
+ REACT_FIELD(focusable)
+ std::optional focusable{};
+
+ REACT_FIELD(isBeakVisible)
+ std::optional isBeakVisible{};
+
+ REACT_FIELD(maxHeight)
+ std::optional maxHeight{};
+
+ REACT_FIELD(maxWidth)
+ std::optional maxWidth{};
+
+ REACT_FIELD(setInitialFocus)
+ std::optional setInitialFocus{};
+
+ REACT_FIELD(testID)
+ std::optional testID;
+
+ REACT_FIELD(directionalHint)
+ std::optional directionalHint;
+
+ REACT_FIELD(target)
+ winrt::Microsoft::ReactNative::JSValue target{nullptr};
+
+ // These fields can be used to determine if JS has registered for this event
+ REACT_FIELD(onRestoreFocus)
+ bool onRestoreFocus{false};
+
+ REACT_FIELD(onDismiss)
+ bool onDismiss{false};
+
+ REACT_FIELD(onShow)
+ bool onShow{false};
+
+ const winrt::Microsoft::ReactNative::ViewProps ViewProps;
+};
+
+REACT_STRUCT(RCTCalloutSpec_onShow)
+struct RCTCalloutSpec_onShow {
+ REACT_FIELD(target)
+ int32_t target{};
+};
+
+REACT_STRUCT(RCTCalloutSpec_onDismiss)
+struct RCTCalloutSpec_onDismiss {
+ REACT_FIELD(target)
+ int32_t target{};
+};
+
+REACT_STRUCT(RCTCalloutSpec_onRestoreFocus)
+struct RCTCalloutSpec_onRestoreFocus {
+ REACT_FIELD(target)
+ int32_t target{};
+
+ REACT_FIELD(containsFocus)
+ bool containsFocus{};
+};
+
+struct RCTCalloutEventEmitter {
+ RCTCalloutEventEmitter(const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter)
+ : m_eventEmitter(eventEmitter) {}
+
+ using OnRestoreFocus = RCTCalloutSpec_onRestoreFocus;
+ using OnDismiss = RCTCalloutSpec_onDismiss;
+ using OnShow = RCTCalloutSpec_onShow;
+
+ void onRestoreFocus(OnRestoreFocus &&value) const {
+ m_eventEmitter.DispatchEvent(L"restoreFocus", [value = std::move(value)](const winrt::Microsoft::ReactNative::IJSValueWriter writer) {
+ winrt::Microsoft::ReactNative::WriteValue(writer, value);
+ });
+ }
+
+ void onDismiss(OnDismiss &&value) const {
+ m_eventEmitter.DispatchEvent(L"dismiss", [value = std::move(value)](const winrt::Microsoft::ReactNative::IJSValueWriter writer) {
+ winrt::Microsoft::ReactNative::WriteValue(writer, value);
+ });
+ }
+
+ void onShow(OnShow &&value) const {
+ m_eventEmitter.DispatchEvent(L"show", [value = std::move(value)](const winrt::Microsoft::ReactNative::IJSValueWriter writer) {
+ winrt::Microsoft::ReactNative::WriteValue(writer, value);
+ });
+ }
+
+ private:
+ winrt::Microsoft::ReactNative::EventEmitter m_eventEmitter{nullptr};
+};
+
+template
+struct BaseRCTCallout {
+
+ 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;
+ }
+
+ // You must provide an implementation of this method to handle the "focusWindow" command
+ virtual void HandleFocusWindowCommand() noexcept = 0;
+
+ // You must provide an implementation of this method to handle the "blurWindow" command
+ virtual void HandleBlurWindowCommand() noexcept = 0;
+
+ void HandleCommand(const winrt::Microsoft::ReactNative::ComponentView &view, const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept {
+ auto userData = view.UserData().as();
+ auto commandName = args.CommandName();
+ if (commandName == L"focusWindow") {
+
+ userData->HandleFocusWindowCommand();
+ return;
+ }
+
+ if (commandName == L"blurWindow") {
+
+ userData->HandleBlurWindowCommand();
+ return;
+ }
+ }
+
+ 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 RegisterRCTCalloutNativeComponent(
+ winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder,
+ std::function builderCallback) noexcept {
+ packageBuilder.as().AddViewComponent(
+ L"Callout", [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 != &BaseRCTCallout::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 != &BaseRCTCallout::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);
+ });
+ }
+
+ builder.SetCustomCommandHandler([](const winrt::Microsoft::ReactNative::ComponentView &view,
+ const winrt::Microsoft::ReactNative::HandleCommandArgs& args) noexcept {
+ auto userData = view.UserData().as();
+ userData->HandleCommand(view, args);
+ });
+
+ if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::MountChildComponentView != &BaseRCTCallout::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 != &BaseRCTCallout::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 != &BaseRCTCallout::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 != &BaseRCTCallout::Initialize) {
+ userData->Initialize(view);
+ }
+ view.UserData(*userData);
+ });
+
+ if CONSTEXPR_SUPPORTED_ON_VIRTUAL_FN_ADDRESS (&TUserData::CreateVisual != &BaseRCTCallout::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::FluentUI::Callout::Codegen
+
+#endif // #ifdef RNW_NEW_ARCH
diff --git a/packages/components/Callout/windows/Callout/packages.lock.json b/packages/components/Callout/windows/Callout/packages.lock.json
new file mode 100644
index 0000000000..17e459d260
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/packages.lock.json
@@ -0,0 +1,151 @@
+{
+ "version": 1,
+ "dependencies": {
+ "native,Version=v0.0": {
+ "boost": {
+ "type": "Direct",
+ "requested": "[1.84.0, )",
+ "resolved": "1.84.0",
+ "contentHash": "4el2YP3cNJDVFPdzOso+LxGvdWP2rHxML4siq8VdonNypW2m4q503tHfCj6vK0L1UfxioE2hpFGb4ITEua73tg=="
+ },
+ "Microsoft.ReactNative": {
+ "type": "Direct",
+ "requested": "[0.81.32, )",
+ "resolved": "0.81.32",
+ "contentHash": "Ks1M8X7hk/yFF0LV4PymQelr1A2PqselgqBIuJIvJgI0bnO0OMXDXqj6C1SDJCAaKUa5U3/G78h+yUPa4EKsJQ=="
+ },
+ "Microsoft.ReactNative.Cxx": {
+ "type": "Direct",
+ "requested": "[0.81.32, )",
+ "resolved": "0.81.32",
+ "contentHash": "7sk1LmAne4vDt5BWUg2drRw85ktACDdBLBMGK2y0iXWwZjayhmzkMBmVhX4zoN6t+bYu/Z2/LfyOVqQJCok7Sg==",
+ "dependencies": {
+ "Microsoft.ReactNative": "0.81.32"
+ }
+ },
+ "Microsoft.VCRTForwarders.140": {
+ "type": "Direct",
+ "requested": "[1.0.2-rc, )",
+ "resolved": "1.0.2-rc",
+ "contentHash": "/r+sjtEeCIGyDhobIZ5hSmYhC/dSyGZxf1SxYJpElUhB0LMCktOMFs9gXrauXypIFECpVynNyVjAmJt6hjJ5oQ=="
+ },
+ "Microsoft.Windows.CppWinRT": {
+ "type": "Direct",
+ "requested": "[2.0.230706.1, )",
+ "resolved": "2.0.230706.1",
+ "contentHash": "l0D7oCw/5X+xIKHqZTi62TtV+1qeSz7KVluNFdrJ9hXsst4ghvqQ/Yhura7JqRdZWBXAuDS0G0KwALptdoxweQ=="
+ },
+ "Microsoft.WindowsAppSDK": {
+ "type": "Direct",
+ "requested": "[1.8.260508005, )",
+ "resolved": "1.8.260508005",
+ "contentHash": "+aA+zrvqJKgsn/1TPOSR0Uy7dkMO5jI/+cDWPu2pWmXe4KQxYkR8gQFY9IrfbgCxxSd/yl1zMcAKD/4HBbNqaw==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.AI": "[1.8.76]",
+ "Microsoft.WindowsAppSDK.Base": "[1.8.251216001]",
+ "Microsoft.WindowsAppSDK.DWrite": "[1.8.25122902]",
+ "Microsoft.WindowsAppSDK.Foundation": "[1.8.260505001]",
+ "Microsoft.WindowsAppSDK.InteractiveExperiences": "[1.8.260430001]",
+ "Microsoft.WindowsAppSDK.ML": "[1.8.2197]",
+ "Microsoft.WindowsAppSDK.Runtime": "[1.8.260508005]",
+ "Microsoft.WindowsAppSDK.Widgets": "[1.8.251231004]",
+ "Microsoft.WindowsAppSDK.WinUI": "[1.8.260505002]"
+ }
+ },
+ "Microsoft.Web.WebView2": {
+ "type": "Transitive",
+ "resolved": "1.0.3179.45",
+ "contentHash": "3pokSH5CnN0G6rGhGFo1y87inxYhNxBQ2Vdf0wlvBj99KHxQJormjDACmqRnFeUsmuNFIhWwfAL1ztq7wD5qRA=="
+ },
+ "Microsoft.Windows.SDK.BuildTools": {
+ "type": "Transitive",
+ "resolved": "10.0.26100.4654",
+ "contentHash": "2mgcOlj/t2RfSyyw+pVESfO+Tk1RkfQzto9Vrq42M1lUQIfQEwbi8QLha9GXWIOj+TFzeHIEJckIoF25mgiM8A=="
+ },
+ "Microsoft.Windows.SDK.BuildTools.MSIX": {
+ "type": "Transitive",
+ "resolved": "1.7.20250829.1",
+ "contentHash": "IMdvRmCIZnBS5GkYnv0po1bcx6U1OF39pqA4TphQ9evDzpCRoSE19/PkDvlUNNrBavTsLIEJgd/TAIFner75ow=="
+ },
+ "Microsoft.WindowsAppSDK.AI": {
+ "type": "Transitive",
+ "resolved": "1.8.76",
+ "contentHash": "Ayn9QybcwzH+c8eQlE7dm2oO3Jrcn2uohLcsHJpCbLOAfcisjzwtSBe0oyulbaJ86R4eDSX3RDS25tsjGpIqyQ==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001",
+ "Microsoft.WindowsAppSDK.Foundation": "1.8.260501000"
+ }
+ },
+ "Microsoft.WindowsAppSDK.Base": {
+ "type": "Transitive",
+ "resolved": "1.8.251216001",
+ "contentHash": "PS1wriuFknz3W2F2P/e6RvOTM35w89Lsj/f0QmUEPrJjKnc+jM0JLX1vfdytI14y1gNRUTm9uclwP0aH/SVU5w==",
+ "dependencies": {
+ "Microsoft.Windows.SDK.BuildTools": "10.0.26100.4654",
+ "Microsoft.Windows.SDK.BuildTools.MSIX": "1.7.20250829.1"
+ }
+ },
+ "Microsoft.WindowsAppSDK.DWrite": {
+ "type": "Transitive",
+ "resolved": "1.8.25122902",
+ "contentHash": "zFNn07i7Cyz62Y8FnPQAyzeZK7ww3m9t42i9pzy4C04pNbyUDQ4fG7pB6VSh6n4EyFuYtuFQuDzt4mKmXFrkrg==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001"
+ }
+ },
+ "Microsoft.WindowsAppSDK.Foundation": {
+ "type": "Transitive",
+ "resolved": "1.8.260505001",
+ "contentHash": "41SSoEn3sKKCAVPA/w18zVJwV1C7aDEkPS2f6Zyp19m27tDEmteEp0XS3Ln3b0ElYR4FfPEPvIDbJPSA9vePGw==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001",
+ "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001"
+ }
+ },
+ "Microsoft.WindowsAppSDK.InteractiveExperiences": {
+ "type": "Transitive",
+ "resolved": "1.8.260430001",
+ "contentHash": "fTPCnQb3ZarMh9khlEfbLDllcZzK0tSP+2S6W/T4cPyLLSAmARm8Gd3AC5kqubnDjzTvG+1lNZ1bZGFsIHplJQ==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001"
+ }
+ },
+ "Microsoft.WindowsAppSDK.ML": {
+ "type": "Transitive",
+ "resolved": "1.8.2197",
+ "contentHash": "6Bc1SOLd5HicY3GbF+zr76YBfH4iZOKeEGxTK/lHKAK2ExZTWavOADxB2CKSi/irF4dWSngUdRFopWPmckJ6fA==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001",
+ "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001"
+ }
+ },
+ "Microsoft.WindowsAppSDK.Runtime": {
+ "type": "Transitive",
+ "resolved": "1.8.260508005",
+ "contentHash": "2JqXzA4heHSkkaXJNyEvYtpv2wsEUMhwQzgXmZsFmYSkUXSdeVc6hdHcWoBK1SY9l1Sjd1brLt7h16kIpgh2kA==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001"
+ }
+ },
+ "Microsoft.WindowsAppSDK.Widgets": {
+ "type": "Transitive",
+ "resolved": "1.8.251231004",
+ "contentHash": "bIWqQYR8DCoB1SoPOMil5AtgtkTn438wJTdpsHgyO/6o7Eh7PMP5BzrR0KbDsFqy+4LhPWQ4vtwko5k93fECcA==",
+ "dependencies": {
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001"
+ }
+ },
+ "Microsoft.WindowsAppSDK.WinUI": {
+ "type": "Transitive",
+ "resolved": "1.8.260505002",
+ "contentHash": "/hGl6EOmo8aeJR2bYbOmGhOicnJK4EY+u+cQI+jm8H1uXThfMX32DsfKOt9LkOcu8AKX3j5FpiBay8ObUSytIw==",
+ "dependencies": {
+ "Microsoft.Web.WebView2": "1.0.3179.45",
+ "Microsoft.WindowsAppSDK.Base": "1.8.251216001",
+ "Microsoft.WindowsAppSDK.Foundation": "1.8.260505001",
+ "Microsoft.WindowsAppSDK.InteractiveExperiences": "1.8.260430001"
+ }
+ }
+ }
+ }
+}
diff --git a/packages/components/Callout/windows/Callout/pch.cpp b/packages/components/Callout/windows/Callout/pch.cpp
new file mode 100644
index 0000000000..bcb5590be1
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/packages/components/Callout/windows/Callout/pch.h b/packages/components/Callout/windows/Callout/pch.h
new file mode 100644
index 0000000000..4eac9041ed
--- /dev/null
+++ b/packages/components/Callout/windows/Callout/pch.h
@@ -0,0 +1,28 @@
+// pch.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#define NOMINMAX 1
+//#define WIN32_LEAN_AND_MEAN 1
+//#define WINRT_LEAN_AND_MEAN 1
+
+// Windows Header Files
+#include
+#undef GetCurrentTime
+#include
+
+// WinRT Header Files
+#include
+#include
+#include
+
+// C RunTime Header Files
+#include
+#include
+#include
+#include
+
+// Reference additional headers your project requires here
diff --git a/packages/components/Callout/windows/NuGet.Config b/packages/components/Callout/windows/NuGet.Config
new file mode 100644
index 0000000000..aee3137fd7
--- /dev/null
+++ b/packages/components/Callout/windows/NuGet.Config
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yarn.lock b/yarn.lock
index 660957ab8e..a0ae34bc95 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3035,6 +3035,7 @@ __metadata:
resolution: "@fluentui-react-native/components@workspace:packages/agentic-components"
dependencies:
"@babel/core": "catalog:"
+ "@fluentui-react-native/callout": "workspace:*"
"@fluentui-react-native/design": "workspace:*"
"@fluentui-react-native/framework-base": "workspace:*"
"@fluentui-react-native/scripts": "workspace:*"
@@ -3051,6 +3052,7 @@ __metadata:
react-native-svg: "npm:^15.12.1"
test-renderer: "catalog:0.81"
peerDependencies:
+ "@office-iss/react-native-win32": ^0.74.0 || ^0.81.0
"@types/react": ~18.2.0 || ~19.0.0 || ~19.1.4
react: 18.2.0 || 19.0.0 || 19.1.4
react-native: ^0.73.0 || ^0.74.0 || ^0.78.0 || ^0.81.6
@@ -3058,6 +3060,8 @@ __metadata:
react-native-svg: ">=15.0.0 <15.13.0 || >=15.4.0 <15.13.0 || ^15.11.2 || ^15.12.1"
react-native-windows: ^0.73.0 || ^0.74.0 || ^0.78.0 || ^0.81.0
peerDependenciesMeta:
+ "@office-iss/react-native-win32":
+ optional: true
"@types/react":
optional: true
react-native-macos: