Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@
{
"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,98 @@
// 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>

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 SetXamlRoot + VisualTreeHelper)

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: Register XamlRoot to enable popup dismissal when scroll begins.
// This is the GENERIC pattern that ANY 3rd party XAML component should use:
// 1. Create your XamlIsland and set its Content
// 2. Call SetXamlRoot() with the content's XamlRoot
// When the parent ScrollView starts scrolling, ContentIslandComponentView will use
// VisualTreeHelper.GetOpenPopupsForXamlRoot() to find and close ALL open popups.
// This works for ComboBox, DatePicker, TimePicker, Flyouts, etc. - any XAML popup!
m_comboBox.Loaded([islandView, this](auto const &, auto const &) {
// XamlRoot is available after the element is loaded
if (auto xamlRoot = m_comboBox.XamlRoot()) {
islandView.SetXamlRoot(xamlRoot);
}
});

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);
}
});
}

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

} // 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
Expand Down Expand Up @@ -27,6 +27,9 @@
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ComboBox.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="SampleCustomComponent.cpp">
Expand All @@ -38,6 +41,9 @@
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ComboBox.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="SampleCustomComponent.rc">
Expand Down
Loading
Loading