Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4de944f
Fix XAML popup positioning and light dismiss in ScrollView (#15557)
Jan 21, 2026
b29d678
Merge branch 'main' into nitinc/fix-xaml-popup-positioning-15557
Nitin-100 Jan 21, 2026
3b3aaaf
Change files
Jan 21, 2026
1df8e5d
Fix XAML popup positioning and light dismiss in ScrollView (#15557)
Jan 25, 2026
7917f1a
Fix XAML popup positioning - convert to DIPs and update synchronously
Jan 25, 2026
6b10e7c
Merge branch 'main' into nitinc/fix-xaml-popup-positioning-15557
Nitin-100 Jan 25, 2026
6d19597
Apply clang-format
Jan 26, 2026
161c2d2
Merge branch 'main' into nitinc/fix-xaml-popup-positioning-15557
Nitin-100 Jan 27, 2026
e8cfe99
Revert IDL formatting, keep only DismissPopupsRequest event
Jan 27, 2026
a82069e
Merge branch 'main' into nitinc/fix-xaml-popup-positioning-15557
Nitin-100 Jan 28, 2026
b9a0fed
Change files
Jan 28, 2026
9c258b5
Optimize light dismiss: register during mount instead of tree walk on…
Jan 28, 2026
f4e09ad
Merge branch 'main' into nitinc/fix-xaml-popup-positioning-15557
Nitin-100 Jan 28, 2026
703e418
docs: fix broken markdown links in pipeline (#15594)
ssuraj2504 Jan 30, 2026
ab15212
Bring Narrator focus to XAML island (#15611)
vineethkuttan Jan 30, 2026
ac0b07c
Merge upstream/main - resolve conflict in ContentIslandComponentView.h
Jan 31, 2026
db01f95
Fix IDL: only add events without formatting changes
Feb 3, 2026
dc3de67
Merge branch 'main' into nitinc/fix-xaml-popup-positioning-15557
Nitin-100 Feb 3, 2026
93bccc7
Merge branch 'main' into nitinc/fix-xaml-popup-positioning-15557
Nitin-100 Feb 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fix XAML popup positioning and light dismiss in ScrollView (#15557)",
"packageName": "@react-native-windows/automation",
"email": "nitchaudhary@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fix XAML popup positioning and light dismiss in ScrollView (#15557)",
"packageName": "@react-native-windows/automation-channel",
"email": "nitchaudhary@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fix XAML popup positioning and light dismiss in ScrollView (#15557)",
"packageName": "@react-native-windows/automation-commands",
"email": "nitchaudhary@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"comment": "Fix XAML popup positioning and light dismiss in ScrollView (#15557)",
"type": "prerelease",
"packageName": "react-native-windows",
"email": "nitchaudhary@microsoft.com",
"dependentChangeType": "patch"
}
152 changes: 152 additions & 0 deletions packages/playground/Samples/xamlPopupBug.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* XAML Popup Positioning Bug Repro - Issue #15557
*
* HOW TO REPRO:
* 1. Run this sample in Playground
* 2. SCROLL DOWN in the ScrollView
* 3. Click on the ComboBox to open the dropdown popup
* 4. BUG: The popup appears at the WRONG position!
*
* The popup offset = how much you scrolled
*/

import React from 'react';
import {AppRegistry, ScrollView, View, Text, StyleSheet} from 'react-native';
import {ComboBox} from 'sample-custom-component';

const XamlPopupBugRepro = () => {
const [selectedValue, setSelectedValue] = React.useState('(click to select)');

return (
<View style={styles.container}>
{/* Header - Fixed at top */}
<View style={styles.header}>
<Text style={styles.title}>XAML Popup Bug Repro #15557</Text>
<Text style={styles.subtitle}>Selected: {selectedValue}</Text>
</View>

{/* Instructions */}
<View style={styles.instructions}>
<Text style={styles.step}>1. SCROLL DOWN in the box below</Text>
<Text style={styles.step}>2. Click a ComboBox to open dropdown</Text>
<Text style={styles.step}>3. See the popup at WRONG position!</Text>
</View>

{/* Scrollable area with ComboBoxes */}
<ScrollView style={styles.scrollView}>
<View style={[styles.spacer, {backgroundColor: '#e74c3c'}]}>
<Text style={styles.spacerText}>SCROLL DOWN</Text>
</View>

<View style={[styles.spacer, {backgroundColor: '#e67e22'}]}>
<Text style={styles.spacerText}>Keep scrolling...</Text>
</View>

<View style={[styles.spacer, {backgroundColor: '#f1c40f'}]}>
<Text style={styles.spacerText}>Almost there...</Text>
</View>

{/* First ComboBox */}
<View style={styles.comboBoxContainer}>
<Text style={styles.comboLabel}>ComboBox 1 - Click me!</Text>
<ComboBox
style={styles.comboBox}
onSelectionChanged={(e) => {
setSelectedValue(`CB1: ${e.nativeEvent.selectedValue}`);
}}
/>
</View>

<View style={[styles.spacer, {backgroundColor: '#2ecc71'}]}>
<Text style={styles.spacerText}>More space...</Text>
</View>

{/* Second ComboBox */}
<View style={styles.comboBoxContainer}>
<Text style={styles.comboLabel}>ComboBox 2 - Click me!</Text>
<ComboBox
style={styles.comboBox}
onSelectionChanged={(e) => {
setSelectedValue(`CB2: ${e.nativeEvent.selectedValue}`);
}}
/>
</View>

<View style={[styles.spacer, {backgroundColor: '#3498db'}]}>
<Text style={styles.spacerText}>End of content</Text>
</View>
</ScrollView>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1a1a2e',
},
header: {
padding: 20,
backgroundColor: '#16213e',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#fff',
},
subtitle: {
fontSize: 16,
color: '#0f0',
marginTop: 5,
},
instructions: {
padding: 15,
backgroundColor: '#0f3460',
},
step: {
fontSize: 18,
color: '#fff',
marginVertical: 3,
},
scrollView: {
flex: 1,
margin: 10,
borderWidth: 3,
borderColor: '#e94560',
borderRadius: 10,
},
spacer: {
height: 200,
justifyContent: 'center',
alignItems: 'center',
margin: 10,
borderRadius: 10,
},
spacerText: {
fontSize: 24,
fontWeight: 'bold',
color: '#fff',
},
comboBoxContainer: {
margin: 10,
padding: 15,
backgroundColor: '#fff',
borderRadius: 10,
borderWidth: 3,
borderColor: '#e94560',
},
comboLabel: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
color: '#1a1a2e',
},
comboBox: {
width: 350,
height: 60,
},
});

AppRegistry.registerComponent('Bootstrap', () => XamlPopupBugRepro);
export default XamlPopupBugRepro;
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,8 @@ struct WindowData {
LR"(Samples\mouse)", LR"(Samples\scrollViewSnapSample)",
LR"(Samples\simple)", LR"(Samples\text)",
LR"(Samples\textinput)", LR"(Samples\ticTacToe)",
LR"(Samples\view)", LR"(Samples\debugTest01)"};
LR"(Samples\view)", LR"(Samples\debugTest01)",
LR"(Samples\xamlPopupBug)"};

static INT_PTR CALLBACK Bundle(HWND hwnd, UINT message, WPARAM wparam, LPARAM /*lparam*/) noexcept {
switch (message) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
* @format
* @flow
*/

'use strict';

// ComboBox component for testing XAML popup positioning bug #15557
// The ComboBox dropdown popup should appear at the correct position after scrolling

import {codegenNativeComponent} from 'react-native';
import type {ViewProps} from 'react-native';
import type {
DirectEventHandler,
Int32,
} from 'react-native/Libraries/Types/CodegenTypes';

type SelectionChangedEvent = Readonly<{
selectedIndex: Int32;
selectedValue: string;
}>;

export interface ComboBoxProps extends ViewProps {
selectedIndex?: Int32;
placeholder?: string;
onSelectionChanged?: DirectEventHandler<SelectionChangedEvent>;
}

export default codegenNativeComponent<ComboBoxProps>('ComboBox');
5 changes: 4 additions & 1 deletion packages/sample-custom-component/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import DrawingIsland from './DrawingIsland';

import CalendarView from './FabricXamlCalendarViewNativeComponent'

import ComboBox from './FabricXamlComboBoxNativeComponent'

import CustomAccessibility from './CustomAccessibilityNativeComponent';

export {
Expand All @@ -13,4 +15,5 @@ export {
MovingLight,
MovingLightHandle,
CalendarView,
};
ComboBox,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ComboBox component for testing XAML popup positioning bug #15557
#include "pch.h"

#include "ComboBox.h"

#if defined(RNW_NEW_ARCH)

#include "codegen/react/components/SampleCustomComponent/ComboBox.g.h"

#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Media.h>

namespace winrt::SampleCustomComponent {

// ComboBox component to test popup positioning issue #15557
// When inside a ScrollView, the dropdown popup should appear at the correct position
// Bug 1: After scrolling, the popup appears at the wrong offset (FIXED via LayoutMetricsChanged)
// Bug 2: When popup is open and user scrolls, popup should dismiss (FIXED via DismissPopupsRequest event)

struct ComboBoxComponentView : public winrt::implements<ComboBoxComponentView, winrt::IInspectable>,
Codegen::BaseComboBox<ComboBoxComponentView> {
void InitializeContentIsland(
const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept {
m_xamlIsland = winrt::Microsoft::UI::Xaml::XamlIsland{};
m_comboBox = winrt::Microsoft::UI::Xaml::Controls::ComboBox{};

// Add default items
m_comboBox.Items().Append(winrt::box_value(L"Option 1 - Select me after scrolling"));
m_comboBox.Items().Append(winrt::box_value(L"Option 2 - Test popup position"));
m_comboBox.Items().Append(winrt::box_value(L"Option 3 - Bug #15557"));
m_comboBox.Items().Append(winrt::box_value(L"Option 4 - Popup should be here"));
m_comboBox.Items().Append(winrt::box_value(L"Option 5 - Not somewhere else!"));

m_comboBox.PlaceholderText(L"Click to open dropdown...");
m_comboBox.FontSize(20);
m_comboBox.HorizontalAlignment(winrt::Microsoft::UI::Xaml::HorizontalAlignment::Stretch);
m_comboBox.VerticalAlignment(winrt::Microsoft::UI::Xaml::VerticalAlignment::Center);

m_xamlIsland.Content(m_comboBox);
islandView.Connect(m_xamlIsland.ContentIsland());

// Issue #15557 Bug 2 Fix: Subscribe to DismissPopupsRequest event to close popups when scroll begins.
// This is the pattern that ANY 3rd party XAML component should use:
// 1. Subscribe to the DismissPopupsRequest event
// 2. When the event fires, use VisualTreeHelper to find and close your open popups
// This works for ComboBox, DatePicker, TimePicker, Flyouts, etc. - any XAML popup!
m_dismissPopupsRequestToken = islandView.DismissPopupsRequest(
[this](winrt::Windows::Foundation::IInspectable const &, winrt::Windows::Foundation::IInspectable const &) {
DismissPopups();
});

m_selectionChangedToken =
m_comboBox.SelectionChanged([this](
winrt::Windows::Foundation::IInspectable const &,
winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const &) {
if (auto emitter = EventEmitter()) {
Codegen::ComboBox_OnSelectionChanged args;
args.selectedIndex = m_comboBox.SelectedIndex();
if (m_comboBox.SelectedItem()) {
auto selectedText = winrt::unbox_value<winrt::hstring>(m_comboBox.SelectedItem());
args.selectedValue = winrt::to_string(selectedText);
} else {
args.selectedValue = "";
}
emitter->onSelectionChanged(args);
}
});
}

// Dismiss any open popups for this component's XamlRoot
void DismissPopups() noexcept {
if (auto xamlRoot = m_comboBox.XamlRoot()) {
auto openPopups = winrt::Microsoft::UI::Xaml::Media::VisualTreeHelper::GetOpenPopupsForXamlRoot(xamlRoot);
for (const auto &popup : openPopups) {
if (popup.IsOpen()) {
popup.IsOpen(false);
}
}
}
}

private:
winrt::Microsoft::UI::Xaml::XamlIsland m_xamlIsland{nullptr};
winrt::Microsoft::UI::Xaml::Controls::ComboBox m_comboBox{nullptr};
winrt::event_token m_selectionChangedToken{};
winrt::event_token m_dismissPopupsRequestToken{};
};

} // namespace winrt::SampleCustomComponent

void RegisterComboBoxComponentView(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) {
winrt::SampleCustomComponent::Codegen::RegisterComboBoxNativeComponent<
winrt::SampleCustomComponent::ComboBoxComponentView>(
packageBuilder,
[](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) {
builder.SetContentIslandComponentViewInitializer(
[](const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept {
auto userData = winrt::make_self<winrt::SampleCustomComponent::ComboBoxComponentView>();
userData->InitializeContentIsland(islandView);
islandView.UserData(*userData);
});
});
}

#endif // defined(RNW_NEW_ARCH)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once

#include <winrt/Microsoft.ReactNative.h>

void RegisterComboBoxComponentView(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#endif

#include "CalendarView.h"
#include "ComboBox.h"
#include "CustomAccessibility.h"
#include "DrawingIsland.h"
#include "MovingLight.h"
Expand All @@ -24,6 +25,7 @@ void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuil
RegisterMovingLightNativeComponent(packageBuilder);
RegisterCalendarViewComponentView(packageBuilder);
RegisterCustomAccessibilityComponentView(packageBuilder);
RegisterComboBoxComponentView(packageBuilder);
#endif // #ifdef RNW_NEW_ARCH
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
<PropertyGroup Label="UserMacros" />
<ItemGroup>
<ClInclude Include="CalendarView.h" />
<ClInclude Include="ComboBox.h" />
<ClInclude Include="CustomAccessibility.h" />
<ClInclude Include="DrawingIsland.h">
<DependentUpon>DrawingIsland.idl</DependentUpon>
Expand All @@ -126,6 +127,7 @@
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClCompile>
<ClCompile Include="CalendarView.cpp" />
<ClCompile Include="ComboBox.cpp" />
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
</ItemGroup>
<ItemGroup>
Expand Down
Loading
Loading