Skip to content

[Windows] HybridWebView: Route JS evaluation through RunAfterInitialize to preserve WebView2 initialization gate#35702

Closed
ne0rrmatrix wants to merge 219 commits into
dotnet:mainfrom
ne0rrmatrix:fix/hybridwebview-windows-init-regression
Closed

[Windows] HybridWebView: Route JS evaluation through RunAfterInitialize to preserve WebView2 initialization gate#35702
ne0rrmatrix wants to merge 219 commits into
dotnet:mainfrom
ne0rrmatrix:fix/hybridwebview-windows-init-regression

Conversation

@ne0rrmatrix
Copy link
Copy Markdown

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description of Change

Fixes a regression introduced by PR #32491 where Windows HybridWebView JS invocation bypassed the WebView2 initialization gate.

Root cause: After #32491, HybridWebViewHelper called handler.PlatformView.EvaluateJavaScript(innerRequest) directly in both EvaluateJavaScriptAsync and InvokeJavaScriptAsync, bypassing the existing RunAfterInitialize(...) gate on Windows.

Fix approach: Use #if WINDOWS\ guards to route JS evaluation through the handler command mapper (\handler.InvokeAsync) for the sync path, and through \hybridPlatformWebView.RunAfterInitialize()\ for the async path. This preserves the WebView2 initialization check that ensures \CoreWebView2\ is ready before executing JavaScript.

Key insight: The Windows \MauiHybridWebView\ platform view has a \RunAfterInitialize\ method that gates JS execution until \CoreWebView2\ is initialized. Direct calls to \handler.PlatformView.EvaluateJavaScript\ bypass this gate entirely.

What NOT to Do (for future agents):

  • Don't call \handler.PlatformView.EvaluateJavaScript\ directly on Windows - It bypasses the WebView2 initialization gate and can cause \InvalidOperationException\ or hangs
  • Don't remove the #if WINDOWS\ guards - This is a Windows-specific regression; other platforms don't need this protection
  • Don't use \RunAfterInitialize\ for non-MauiHybridWebView types - Always check the platform view type first before casting

Issues Fixed

Fixes #35696

KarthikRajaKalaimani and others added 30 commits May 6, 2026 09:20
…net#34527)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:

Horizontalspacing / Verticalspacing is not not applied to the first
column in GridItemLayout using CollectionView on Android platform.
        
### Root Cause:

The grid spacing was not being distributed symmetrically across the
active layout implementations, so edge items did not fully participate
when spacing changed at runtime.

### Description of Change:

- On Android, the fix in MauiRecyclerView.cs changes how RecyclerView
padding is handled for GridItemsLayout. Android was already using
SpacingItemDecoration, which applies half-spacing on all four sides of
each item. Previously, negative RecyclerView padding canceled that
spacing at the control edges. The branch keeps that negative-padding
behavior for non-grid layouts, but disables it for GridItemsLayout,
allowing the grid’s half-spacing to remain visible at the outer
perimeter. This makes the first row and first column visually respond
when spacing changes, but it also changes the grid behavior from spacing
only between items to spacing around the outside edges as well.

**Tested the behavior in the following platforms:**

- [x] Android
- [x] Windows
- [ ] iOS
- [ ] Mac

### Reference:

N/A

### Issues Fixed:

Fixes  dotnet#34257      

### Screenshots
| Before  | After  |
|---------|--------|
| <Video
src="https://github.com/user-attachments/assets/578dda69-1d60-474c-a6d8-23b3f9d29a50"
Width="300" Height="600"> | <Video
src="https://github.com/user-attachments/assets/7f3826e6-5922-4b6f-a6b9-de581b7db6c3"
Width="300" Height="600"> |
…otnet#32491)

<!-- Please let the below note in for people that find this PR -->
   > [!NOTE]
   > Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank
   you!

   ## Summary

Fixes a critical bug where `HybridWebView.InvokeJavaScriptAsync` would
timeout when passing JSON strings as parameters. The issue was caused by
improper JavaScript string escaping. The fix extracts business
logic into a new `HybridWebViewHelper` class and uses
`WebViewHelper.EscapeJsString` for proper escaping.

   **Fixes**: dotnet#32438

   ---

   ## The Problem

Issue dotnet#32438 reported that passing JSON strings to
`InvokeJavaScriptAsync` caused timeouts on Windows (required base64
encoding as workaround), while Android worked correctly.

   ### Root Cause

The old `EvaluateJavaScriptAsync` implementation used naive string
concatenation:

   ```csharp
   // OLD - Breaks when script contains quotes
   script = "try{eval('" + script + "')}catch(e){'null'};";
   ```

When the script contained JSON with quotes, this produced invalid
JavaScript:
   ```javascript
try{eval('window.HybridWebView.__InvokeJavaScript(1, 'method',
["{\"userId\":\"value\"}"])')}catch(e){'null'};
              ↑ BROKEN - conflicting quotes
   ```

Result: JavaScript execution failed, causing `InvokeJavaScriptAsync` to
timeout.

   ---

   ## The Solution

   ### Core Fix

   New implementation properly escapes JavaScript strings:

   ```csharp
   // NEW - Uses proper escaping
   var escapedScript = WebViewHelper.EscapeJsString(script);
   var wrappedScript = $$"""
   (function() {
       try {
let result = eval('{{escapedScript}}'); // ← Properly escaped
return JSON.stringify({ IsError: false, Result: JSON.stringify(result)
});
       } catch (error) {
           // ... error handling
       }
   })()
   """;
   ```

   ### Refactoring

Extracted ~360 lines from `HybridWebViewHandler` into new
`HybridWebViewHelper` class:
- `ProcessEvaluateJavaScriptAsync` - Script wrapping with proper
escaping
   - `ProcessInvokeJavaScriptAsync` - JavaScript call building
   - `ProcessInvokeDotNetAsync` - .NET method invocation from JS
   - `ProcessRawMessage` - Message routing

   Handler reduced from ~600 to 244 lines.

   ---

   ## Changes

   **New Files:**
   - ✨ `HybridWebViewHelper.cs` (470 lines) - Centralized business logic

   **Modified Files:**
   - 🔧 `HybridWebViewHandler.cs` - Delegates to helper
   - 🔧 `HybridWebViewHandler.Standard.cs` - Updated message processing
   - 🔧 `HybridWebViewHandler.Tizen.cs` - Updated message processing

   **Tests:**
   - 🔧 `HybridWebViewTestsBase.cs` - Added 15-second timeout
   - ✨ Added 5 new tests for JSON parameter scenarios
   - ✨ Added 3 enhanced quote-handling tests
   - 🔧 Renamed 20+ tests for clarity
   - 📝 Corrected error messages to use `InvokeJavaScriptAsync`

   **Test HTML:**
- Added `EchoJsonParameter`, `ParseAndStringifyJson`,
`ConcatenateJsonStrings`, `DecodeBase64AndEcho`, `CountJsonArrayItems`

   ---

   ## Testing

   **Platforms**: iOS, Android, Windows, MacCatalyst

   **Scenarios**:
   - JSON with quotes and special characters
   - Complex nested JSON
   - Multiple JSON parameters
   - Large arrays (100 items)
   - Base64 workaround (backward compatibility)

   ---

   ## Breaking Changes

   **None** - Fully backward compatible:
   - ✅ Existing code works unchanged
   - ✅ Base64 workaround still works
   - ✅ No public API changes

   ### Migration Note

   If using base64 workaround, you can now simplify:

   ```csharp
   // OLD workaround (still works):
   var json = JsonSerializer.Serialize(obj);
   var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
await hybridWebView.InvokeJavaScriptAsync<string>("fn", ..., [base64],
...);

   // NEW (now works directly):
   var json = JsonSerializer.Serialize(obj);
await hybridWebView.InvokeJavaScriptAsync<string>("fn", ..., [json],
...);
   ```

   ---

   ## Checklist

   - [x] Code follows .NET MAUI conventions
   - [x] 8 new/enhanced tests added
   - [x] 20+ tests renamed for clarity
   - [x] No breaking changes
   - [x] Backward compatible
   - [x] Error messages corrected
   - [x] Formatted with `dotnet format`
   - [x] XML documentation complete
   - [x] Tested on multiple platforms

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
… on collection update (dotnet#31275)

<!-- Please let the below note in for people that find this PR -->
   > [!NOTE]
   > Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
  Thank you!

### Root Cause
**Windows**
Position not updating on item add: CarouselView stayed at the old
position after an item was added, leaving current/previous positions
unsynced.

Cascading events: With `ItemsUpdatingScrollMode.KeepItemsInView`,
programmatic smooth scrolls triggered multiple ViewChanged calls,
causing `PositionChanged` to fire repeatedly with intermediate values.

**Android**
Programmatic smooth scrolls produced the same cascading
`PositionChanged` events as on Windows.

### Description of Change

**Windows**
Position Update: On item add, `ItemsView.Position` is explicitly set
based on `ItemsUpdatingScrollMode`, keeping current and previous
positions in sync.

Prevent Cascading Events: Added `_isInternalPositionUpdate`. For
collection changes, animations are disabled (animate = false) so
scrolling jumps directly, firing `PositionChanged` only once.

**Android**
Reused the `_isInternalPositionUpdate` logic. Disabled animations during
collection changes, ensuring a single clean position update without
duplicate events.

### Issues Fixed
Fixes dotnet#29529

Tested the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Screenshots
| Before Issue Fix | After Issue Fix |
|------------------|-----------------|
| <img width="350" alt="withoutfix"
src="https://github.com/user-attachments/assets/d66f0352-a91f-4b85-bb9f-e0e54e55aa5f"
/> | <img width="350" alt="withfix"
src="https://github.com/user-attachments/assets/d120f268-6954-498d-aab0-42bc3745e296"
/> |

---------
…3953)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
On iOS/MacCatalyst 26+, when the app theme changes (light ↔ dark), the
Switch's ThumbColor resets to white instead of keeping the custom color.

### Root Cause
On iOS/MacCatalyst 26+, UIKit resets the UISwitch's ThumbTintColor to
its default value during theme transitions.

### Description of Change
Register for trait collection changes to detect theme switches, then
re-apply the custom ThumbColor after UIKit completes its styling.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#33783 
Fixes dotnet#33767 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/aa595096-4f75-4d7e-b31e-f1f0acc24208"
>| <video
src="https://github.com/user-attachments/assets/386f9e6e-6df7-40c5-a3e7-59a0bde31df6">|
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
WebView does not scroll when placed inside a ScrollView. The parent
ScrollView intercepts vertical touch gestures, preventing the WebView
from scrolling its internal content.

### Root Cause
The parent ScrollView was intercepting all touch events without checking
if the child WebView needed to scroll. This prevented the WebView from
receiving touch events and handling its own scrolling.

### Description of Change
Added touch event handling to prevent parent interception when WebView
scrolls. When touch begins or continues, WebView requests exclusive
control from parent. When touch ends or cancels, control returns to
parent. This enables WebView scrolling while preserving normal
parent-child interaction.
 
Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#32971  

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/12b3ca6e-582d-4a50-9ea1-f49027f2d907"
>| <video
src="https://github.com/user-attachments/assets/2fbfd03c-4432-49e9-8b2d-6e7643f57487">|
… to null (dotnet#34741)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
On iOS and macCatalyst, setting BackgroundColor = null on an Entry or
Editor at runtime does not restore the native default appearance. Once a
custom color is applied, it persists even after the property is cleared.

### Root Cause
The shared iOS background update logic only resets BackgroundColor =
null for LayoutView subclasses — all other UIView types silently return.
MauiTextField (Entry) and MauiTextView (Editor) are not LayoutView
subclasses, so the reset is skipped.

### Description of Change
Platform-specific MapBackground methods were added to
EntryHandler.iOS.cs and EditorHandler.iOS.cs. When
Background.IsNullOrEmpty(), they now explicitly set
platformView.BackgroundColor = null to restore native appearance.
 
Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#34611   

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/5ca30c6d-c069-4c04-989b-4dae36584cb4"
>| <video
src="https://github.com/user-attachments/assets/ee9e2a2e-c210-47cc-9f85-2526780d398b">|

---------

Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…t placed child to the Border control in iOS/ Mac platform (dotnet#33330)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:

Stacklayout is not rendered when clip is applied and StackLayout placed
child to the Border control in iOS/ Mac platform.
       
### Root Cause:

When the clip is applied to the StackLayout, its ContainerView
(WrapperView) is being inserted at index 0 to its parent control Border,
which places it below Border's background layer in the z-order. As a
result, the stacklayout is not visible in the view in iOS and Mac
platform.

### Description of Change:

The wrapper view is brought to the front of its parent’s subview stack
so it renders above the Border background in the Z order.

**Tested the behavior in the following platforms.**

- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Reference:

N/A

### Issues Fixed:

Fixes  dotnet#33241    

### Screenshots
| Before  | After  |
|---------|--------|
| <img width="369" height="606" alt="image"
src="https://github.com/user-attachments/assets/0be8bc27-5de4-41ad-a41f-92581513ac55"
/> | <img width="369" height="606" alt="image"
src="https://github.com/user-attachments/assets/6a3590f6-2763-473a-aa91-ee1113e48ec3"
/> |
…cription is set (dotnet#33979)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause
WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to
UI Automation. `ContentPanel` uses the default
`FrameworkElementAutomationPeer`, which exposes both the parent’s
`AutomationProperties.Name` and all child elements. Unlike Android
(`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows
has no single property to hide descendants while keeping the parent
accessible. As a result, both Tab navigation and Browse mode announced
duplicate content.

### Description of Change
Implemented a custom `ContentPanelAutomationPeer` that overrides three
core UI Automation methods (GetAutomationControlTypeCore ,
`GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally
modify behavior when Description is present.
 
When a Description exists, the control is exposed as
`AutomationControlType.Text` (enables browse mode navigation;
alternatives like Custom announce "custom" suffix, Group causes browse
mode to skip the element), the "text" announcement suffix is suppressed
via empty `GetLocalizedControlTypeCore()` return, and child elements are
hidden by returning null from `GetChildrenCore()` to prevent
duplication. When no Description is present, default behavior is
preserved with `AutomationControlType.Custom` and children remain
accessible.
The `HasDescription` helper property centralizes the non-empty
Description check across all three override methods, ensuring consistent
conditional logic following the MAUI `AutomationPeer patterns.

### Issues Fixed

Fixes dotnet#33373 

### Platforms Tested

- [ ] iOS
- [x] Android  
- [x] Windows
- [x] Mac

### Screenshots
| Before Fix | After Fix |
|------------|-----------|
| <video width="350" alt="withoutfix"
src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7"
/> | <video width="350" alt="withfix"
src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6"
/> |

---------
…#33459)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

<img width="870" height="73" alt="image"
src="https://github.com/user-attachments/assets/e27cb50d-db93-431b-831c-24f7faedf9b1"
/>

Invalidation propagation can be quite consuming especially when
switching binding context and multiple properties change (i.e. having
multiple labels inside a layout, all of them changing the text).

Ti PR avoids useless propagations (same behavior `requestLayout`
intrinsically has on Android).
# Conflicts:
#	src/Core/src/Platform/iOS/MauiView.cs
…g lifecycle transition (dotnet#34901)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

## Description

Fixes dotnet#34900

Replaces an opaque `NullReferenceException` with an informative
`InvalidOperationException` in `ContainerView` when the Android
`Context` is no longer available.

### Root Cause

`IMauiContext.Context` (the Android `Context`) is stored via a
`WeakReference` to the Activity. During lifecycle transitions (e.g.,
Activity being GC'd), this reference can become null. When
`NavigationRootManager.Connect()` calls
`view.ToContainerView(mauiContext)`, it creates a `ContainerView` whose
base `LinearLayout` constructor receives a null `Context`, causing an
NRE deep in the JNI interop layer.

### Fix

Added a null-coalescing throw expression in the `ContainerView`
constructor:

```csharp
public ContainerView(IMauiContext context)
    : base(context.Context ?? throw new InvalidOperationException(
        "Unable to create a ContainerView: the Android Context is no longer available. " +
        "This can occur when the Activity has been collected during a lifecycle transition."))
```

This provides a clear, actionable error message instead of the cryptic
NRE that pointed at `NavigationRootManager.Connect` line 60.

### Stack trace from the original crash

```
[AndroidRuntime] at Microsoft.Maui.Platform.NavigationRootManager.Connect
[AndroidRuntime] at Microsoft.Maui.Handlers.WindowHandler.CreateRootViewFromContent
[AndroidRuntime] at Microsoft.Maui.Handlers.WindowHandler.MapContent
```

---------
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Root Cause: 
CarouselView does not utilize the ItemSpacing property during layout
styling
### Description of Change
Added an item container style based on the horizontal or vertical
orientation of the CarouselView, and applied a corresponding style to
the ListViewBase
<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#29772 
### Tested the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

### Screenshot

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <img
src="https://github.com/user-attachments/assets/4af88696-c5a6-4683-bceb-1933781368f3">
| <img
src="https://github.com/user-attachments/assets/f55c2b45-22c3-4ab2-aae5-c668adaf33e4">
|
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail

RadioButtonGroup is not functioning correctly when RadioButton controls
are placed inside a ContentView in .NET 10.

### Root Cause

PR [dotnet#32640](dotnet#32604) (fixing dotnet#32466)
removed the coerceValue and Value = this initialiser from RadioButton,
so RadioButton.Value now defaults to null. This introduced two distinct
bugs in the ContentView+ControlTemplate scenario:

- AddRadioButton guard object.Equals(radioButton.Value,
this.SelectedValue) evaluates to object.Equals(null, null) == true,
causing every button added via a ControlTemplate to be auto-checked
immediately.
- When the layout is not yet attached to a Page (common during
ControlTemplate inflation), GetVisualRoot() returns null. The original
fallback radioButton.Parent resolves to the button's immediate parent
(Border), which only contains that single button — so other RadioButtons
in the same group are never found and never unchecked.

### Description of Change

- Updated the equality check in AddRadioButton to ensure comparison only
happens when SelectedValue is not null. This avoids unintended selection
during initialization.
- Improved the root resolution logic in UncheckOtherRadioButtonsInScope.
If no visual root is found, fallback to the RadioButtonGroupController’s
layout (group container).

### Issue Fixed
Fixes dotnet#34759 

### Screenshots

| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/39e1e45d-dd04-42b7-bb85-aea83124b0cb">
|   <video
src="https://github.com/user-attachments/assets/252587b2-10ae-4eb9-968e-de114756aa0c"> 
|

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
### Description of Change

I tried to improve the access time on BindableProperty given they're
being accessed a lot during the application usage.
As we can see using Array + SIMD is actually performing better when the
number of BindableProperty set on a BindableObject is less than ~10,
though it becomes worse after that.
Array + SIMD also (obviously) allocates less memory.

Considering that SIMD may perform differently on different platforms I
felt it would be better to simply improve the current Dictionary based
implementation by leveraging:
- integer keys
- CollectionMarshal on GetOrAdd

### Benchmarks

| Strategy | PropertiesToSet | Mean | Error | StdDev | Gen0 | Gen1 |
Allocated |
|----------------------- |----------------
|------------:|---------:|---------:|-------:|-------:|----------:|
| Dictionary<BindableProperty> | 1 | 65.22 ns | 0.246 ns | 0.218 ns |
0.0889 | 0.0001 | 744 B |
| Array + SIMD | 1 | **52.93 ns** | 0.750 ns | 0.665 ns | 0.0678 | - |
568 B |
| Dictionary<int,> | 1 | 59.29 ns | 0.563 ns | 0.440 ns | 0.0889 |
0.0001 | 744 B |
| |
| Dictionary<BindableProperty> | 3 | 150.70 ns | 0.344 ns | 0.322 ns |
0.1500 | 0.0005 | 1256 B |
| Array + SIMD | 3 | **122.09 ns** | 0.163 ns | 0.127 ns | 0.1290 |
0.0002 | 1080 B |
| Dictionary<int,> | 3 | 133.50 ns | 0.231 ns | 0.180 ns | 0.1500 |
0.0005 | 1256 B |
| |
| Dictionary<BindableProperty> | 8 | 426.76 ns | 0.686 ns | 0.641 ns |
0.3662 | 0.0033 | 3064 B |
| Array + SIMD | 8 | **318.26 ns** | 0.438 ns | 0.409 ns | 0.3028 |
0.0024 | 2536 B |
| Dictionary<int,> | 8 | 338.98 ns | 2.381 ns | 1.988 ns | 0.3662 |
0.0038 | 3064 B |
| |
| Dictionary<BindableProperty> | 15 | 773.49 ns | 2.593 ns | 2.298 ns |
0.5798 | 0.0095 | 4856 B |
| Array + SIMD | 15 | 665.18 ns | 0.846 ns | 0.791 ns | 0.5531 | 0.0076
| 4632 B |
| Dictionary<int,> | 15 | **581.04 ns** | 0.431 ns | 0.360 ns | 0.5798 |
0.0095 | 4856 B |
| |
| Dictionary<BindableProperty> | 30 | 1,542.20 ns | 3.342 ns | 3.126 ns
| 1.1692 | 0.0381 | 9784 B |
| Array + SIMD | 30 | 1,419.83 ns | 1.301 ns | 1.154 ns | 1.0796 |
0.0324 | 9032 B |
| Dictionary<int,> | 30 | **1,165.86 ns** | 1.599 ns | 1.496 ns | 1.1692
| 0.0381 | 9784 B |
| |
| Dictionary<BindableProperty> | 50 | 2,648.69 ns | 3.759 ns | 2.935 ns
| 2.0828 | 0.1183 | 17448 B |
| Array + SIMD | 50 | 2,587.54 ns | 3.111 ns | 2.429 ns | 1.8196 |
0.0916 | 15224 B |
| Dictionary<int,> | 50 | **1,997.17 ns** | 2.174 ns | 2.033 ns | 2.0828
| 0.1183 | 17448 B |
…otnet#33891)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root Cause
When a password `Entry` field is empty and text is programmatically set,
the obfuscation logic calculates a negative insert position `(0 - 4 =
-4)`. String. `Insert()` doesn't accept negative indices, causing an
`ArgumentOutOfRangeException` crash in windows.

### Description of Change

Added `Math.Max(0, ...)` to clamp the insert position to a minimum of 0,
preventing negative **values.**

### Issues Fixed

Fixes dotnet#33334 

### Platforms Tested

- [x] iOS
- [x] Android  
- [x] Windows
- [x] Mac

### Screenshots
| Before Fix | After Fix |
|------------|-----------|
| <img width="350" alt="withoutfix"
src="https://github.com/user-attachments/assets/43993544-7700-4bff-a04c-d34b999ac962"
/> | <img width="350" alt="withfix"
src="https://github.com/user-attachments/assets/e7f12e20-1cd8-45e7-b07d-bfe7ebac6248"
/> |
…eaving custom state (dotnet#33346)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root Cause:

The issue occurs because platform handlers on iOS and Android do not
restore button properties to their default values when VisualState
setters are removed. In .NET MAUI, when a control transitions from a
custom VisualState back to the normal state, property values are set to
null to indicate that defaults should be restored. However, the platform
handlers interpreted these null values as instructions to take no
action, causing previously applied custom values to remain.

### Fix Description:

The fix involves updating button-specific extension methods to ensure
properties are correctly restored to their platform default values when
property values are null.

For iOS, the UpdateBackground extension method now explicitly handles
UIButton instances. When the background is null or empty,
RemoveBackgroundLayer() is called first to prevent stacking gradient
layers, and the background is reset to UIColor.Clear. This restores
proper transparency and aligns with the default iOS button appearance.
When a valid background is provided, the update continues through the
existing view background logic.

The UpdateTextColor method was also improved to restore the system
default text color correctly when TextColor is null. If the button is
attached to a UIWindow, the handler clears explicit overrides using
SetTitleColor(null, …) for the Normal, Highlighted, and Disabled states,
allowing iOS to fall back to its natural appearance-proxy behavior. It
then restores TintColor using window.TintColor rather than a hardcoded
system color. This ensures the app’s global tint is respected and
prevents clearing appearance settings during the initial render phase.

For Android, a MaterialButton-specific UpdateTextColor overload was
introduced in MauiMaterialButton. The default Material theme
ColorStateList is cached as DefaultTextColors during construction,
before any MAUI property mapping occurs. When TextColor becomes null,
the cached state list is restored directly, preserving all theme-defined
states (normal, disabled, etc.). This approach avoids creating temporary
controls and guarantees consistent restoration of the original Material
theme colors.

### Issues Fixed
Fixes dotnet#19690

### Tested the behaviour in the following platforms
- [x] Mac
- [x] Windows
- [x] iOS
- [x] Android

### Output Screenshot
| Platform | Before Fix | After Fix |
|----------|----------|----------|
| Android | <video
src="https://github.com/user-attachments/assets/d96c404f-99b0-4dd2-bfdf-01adf629466e">
| <video
src="https://github.com/user-attachments/assets/e85313ef-3155-42d3-a1bc-c1b6ef8587b0">
|
|iOS | <video
src="https://github.com/user-attachments/assets/416724b2-f8fe-4146-be56-12c3f6808693">
| <video
src="https://github.com/user-attachments/assets/962ce83a-de7f-4ca0-8339-c8ce88e99288">
|
…tating the device with dialog open (dotnet#31910)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details

- On Android, when opening a TimePicker and then rotating the device,
the picker dialog doesn’t seem to redraw itself to match the new screen
dimensions.

### Root Cause of the issue

- TimePicker lacks orientation change detection entirely. When the
device rotates while the dialog is open, TimePicker has no mechanism to
dismiss and re-show the dialog with updated layout, unlike DatePicker
which detects orientation changes and refreshes the dialog display.

### Description of Change

- Added ConnectHandler and related event subscriptions
(ViewAttachedToWindow/ViewDetachedFromWindow) to manage display info
changes and cleanup when the view is attached or detached. This helps
ensure the time picker dialog responds to device orientation changes and
releases resources properly.

- Implemented OnMainDisplayInfoChanged to dismiss and recreate the time
picker dialog with the current time when the device orientation changes,
preserving user selection progress.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#31658 

### Reference

-
[DatePickerHandler](https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs)

### Tested the behaviour in the following platforms

- [ ] - Windows
- [x] - Android
- [ ] - Mac
- [x] - iOS

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/55091d04-0aa3-4794-aab8-fb5dfc71624e">
| <video
src="https://github.com/user-attachments/assets/56c01750-00a1-4c63-8788-ca6c35d7dbd5">
|

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue details

Changing the Location property on a Microsoft.Maui.Controls.Maps.Pin
does not change the location of the Pin on the map.

### Root cause
`MapPinHandler.Android.cs` was only updating the `MarkerOptions` object
when the Location property changed
The actual `Marker` object already added to the map was not being
updated, causing the pin to stay in its original location

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of change

* **Improved marker association logic**: Updated the `AddPins` method in
`MapHandler` to store marker references in the `MapPinHandler` for
future property updates, ensuring tighter integration between pins and
markers.

Validated the behaviour in the following platforms

- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#12916

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

### Output
| Before| After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/83bedab7-846b-41c1-872e-b6e0d0cd81a4">
| <video
src="https://github.com/user-attachments/assets/fd86fd38-6619-4cf9-999a-de9d05b21e17">
|

---------

Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…#34687)

## Description

Fixes dotnet#19866

On iOS, tapping the status bar should scroll the topmost `UIScrollView`
to the top. This was not working for `CollectionView`, particularly when
hosted inside a `Shell`.

## Root Cause

iOS disables its scroll-to-top behavior when **multiple** `UIScrollView`
instances in the view hierarchy have `scrollsToTop = true`. The Shell
flyout's internal `AccessibilityNeutralTableView` (a `UITableView`
subclass) defaults `scrollsToTop` to `true`, conflicting with the
`CollectionView`'s `UICollectionView`.

## Fix

Two changes:

1. **`ItemsViewController2.ViewDidLoad()`** — Explicitly set
`CollectionView.ScrollsToTop = true` to opt in to scroll-to-top behavior
2. **`ShellTableViewController.AccessibilityNeutralTableView`** — Set
`ScrollsToTop = false` on Shell's flyout table view to prevent the
multi-scroll-view conflict

This follows the existing codebase pattern where `ScrollsToTop` is
explicitly managed (e.g., `ShellSectionRootHeader` and
`ContextActionCell` both set it to `false`).

## Validation

Verified with a Sandbox app using a grouped `CollectionView` inside a
`Shell` with `TabBar`:
- **Without fix**: Diagnostic dump shows two scroll views with
`scrollsToTop=True` → status bar tap does nothing
- **With fix**: Only `MauiCollectionView` has `scrollsToTop=True` →
status bar tap scrolls to top correctly

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ht space above the tab bar even if the page title is empty (dotnet#30382)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root cause

The root cause of this issue is that the Windows Shell implementation
always reserves space for the header area when
Shell.FlyoutBehavior="Flyout" is set, even when pages have no title or
header content. The NavigationView control allocates a fixed height
above the tab bar for the header region without checking if the current
page actually needs this space. This results in an unwanted gap between
the flyout toggle area and the tab bar when navigating to pages with
empty titles, creating visual inconsistency compared to
FlyoutBehavior="Disabled" mode where no header space is reserved for
empty content.

### Description of Issue Fix

The fix involves adding header visibility logic to the Windows Shell
implementation to remove unwanted space above the tab bar in flyout
mode. The implementation introduces three key methods in
RootNavigationView.cs: UpdateHeaderVisibility() to evaluate header
visibility requirements, IsHeaderContentEmpty() to determine if the
toolbar contains no title or title view when in flyout mode, and
CollapseEmptyHeader() to properly hide empty headers. The toolbar
property setter now calls UpdateHeaderVisibility() when the toolbar
changes, and ShellView.cs triggers header visibility updates during tab
navigation. This ensures empty headers don't occupy unnecessary space in
flyout mode while maintaining normal behavior when headers contain
content, resolving the spacing issue above the tab bar when switching
between pages in Shell applications on Windows.

Tested the behavior in the following platforms.
 
- [x] Windows
- [x] Mac
- [x] iOS
- [x] Android

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#30254

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

### Resaved Test snapshots

Resaved the below test snapshot because the ContentPage did not have a
title. So, resaved the test snapshot based on the fix.

1. ShellFlowDirectionUpdate
2. VerifyFlyoutBackgroundColor
3. VerifyHamburgerIcon
4. Issue23834FlyoutMisbehavior

### Output

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/ca496f96-2500-429f-8720-a8adb7925fce">
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/1f0dedd2-4808-46bf-98cf-843ca70294ef">
|
…forms (dotnet#30369)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Detail
The FlowDirection property is not respected by the TimePicker control.
Setting FlowDirection="RightToLeft" has no visual effect on the control
across any platform.

### Root Cause
iOS: The MapFlowDirection method incorrectly used the concrete
TimePickerHandler type instead of the ITimePickerHandler interface. This
bypassed platform-specific logic and defaulted to
ViewHandler.MapFlowDirection, which lacked the necessary text alignment
handling.

Android: The FlowDirection property was not correctly mapped to the
native implementation, resulting in incorrect or missing text alignment
updates.

### Description of Change
iOS: Updated the MapFlowDirection method to use the correct handler
interface. Implemented alignment logic in TimePickerExtensions.cs using
EffectiveFlowDirection to properly align text for both RTL and LTR
layouts.

Android: Implemented a platform-specific MapFlowDirection method in
TimePickerHandler.Android.cs:
For 12-hour format, UpdateTextAlignment is applied to align localized
"AM/PM" text based on FlowDirection.
For 24-hour format, alignment is handled via UpdateFlowDirection, which
sets LayoutDirection and TextDirection to align purely numeric content

Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows dotnet#30322 
- [x] iOS
- [ ] Mac dotnet#30322 

### Issues Fixed:
Fixes dotnet#30192 

### Screenshots

| Before  | After |
|---------|--------|
|  <img
src="https://github.com/user-attachments/assets/37c4a442-1011-4c23-bc2e-8743bc11adc7">
|  <img
src="https://github.com/user-attachments/assets/91b69287-b8eb-4d40-b8a1-0734ef1f89db"> 
|
…dotnet#26217)

### Root cause

The issue arises because the OnNavigationViewSizeChanged method fails to
properly reset the layout measurements before arranging the
NavigationView. As a result, the NavigationView does not correctly
update its layout in response to size changes, causing misalignment or
rendering issues in the ScrollView.

### Description of Issue Fix

The fix involves updating the OnNavigationViewSizeChanged() method to
include a call to InvalidateMeasure() before arranging the
NavigationView. This ensures that the layout is accurately recalculated,
allowing the ScrollView and other elements within the TabbedPage to be
properly measured and arranged during the subsequent layout cycle. This
effectively resolves alignment and rendering issues.

Additionally, the Arrange() call is retained within the SizeChanged
handler to prevent test failures, specifically avoiding timeout issues
observed in the ChangingToNewMauiContextDoesntCrash test. This
combination ensures stable layout behavior while resolving the clipping
and scrolling issues that occur after window resizing.

### Why Tests were not added:

**Regarding the test case:** The issue only occurs when resizing the
window, so it is not possible to add a test case for the window resizing
behavior.

Tested the behavior in the following platforms.
 
- [x] Windows
- [x] Mac
- [x] iOS
- [x] Android

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#26103
Fixes dotnet#11402
Fixes dotnet#20028

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

### Resaved Test snapshots
 
Resaved the below-mentioned test snapshot because elements in the
TabbedPage were not properly aligned before the fix. The layout changes
in OnNavigationViewSizeChanged (adding Arrange() after
InvalidateMeasure()) now ensure proper element alignment within the
TabbedPage.
 
1. DefaultSelectedTabTextColorShouldApplyProperly
2. FontImageSourceColorShouldApplyOnTabIcon
3. VerifyTabbedPageMenuItemTextColor
4. DynamicFontImageSourceColorShouldApplyOnTabIcon
5. Issue1323Test
6. TabBarIconsShouldAutoscaleTabbedPage

### Output

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/67d34cc9-323c-4a8d-afc6-dbcceb558ee5">
| <video width="270" height="600"
src="https://github.com/user-attachments/assets/0ddb5ce5-300a-4088-98f4-f9a3981d6951">
|

---------
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Root Cause:
The UpdateCharacterSpacing method didn't apply spacing to the individual
text blocks in TimePicker

### Description of Change 
Enhanced the UpdateCharacterSpacing method to apply CharacterSpacing to
individual text blocks (HourTextBlock, MinuteTextBlock, and
PeriodTextBlock) within the TimePicker. This ensures the property works
correctly even when the control is loaded asynchronously.

<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#30199 

### Tested the behaviour in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [ ] Mac

### Screenshot

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <img width="1217" height="915" alt="beforeFix30199"
src="https://github.com/user-attachments/assets/0b955bf7-5e75-48d3-8455-3fe0cf2c0c5b">
| <img width="1261" height="946" alt="Screenshot 2025-07-10 155258"
src="https://github.com/user-attachments/assets/5d2d8a34-fb01-4c58-9fef-be3ad10b7cb0">
|

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

---------
…tnet#34383)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Root Cause:
HTML text is applied asynchronously in .NET MAUI on iOS, causing the
CollectionView cell to render first and then re-measure after the HTML
is applied, which leads to visible resizing (scroll jitter).
### Description of Change
Improvements to HTML text handling in CollectionView:

* Updated `UpdateText` method in `LabelExtensions.cs` to check if the
`UILabel` is inside a CV2 cell using the new
`IsPlatformLabelInsideCV2Cell` method, allowing synchronous HTML text
updates when safe and avoiding unnecessary layout passes.
* Added the `IsPlatformLabelInsideCV2Cell` helper method to walk the
UIKit superview chain and detect CV2 cells, improving reliability and
preventing crashes in CV1 layouts.
* Added a reference to `Microsoft.Maui.Controls.Handlers.Items2` to
support the new CV2 cell detection logic.
<!-- Enter description of the fix in this section -->
### Why Tests were not added:
This bug occurs only during live scrolling of CollectionView cells on
iOS when a Label with TextType="Html" is rendered. The issue depends on
the precise timing of asynchronous HTML text application, which triggers
a second layout pass while cells are visible, causing scroll jitter.
Automated tests cannot reliably reproduce this because no framework can
simulate real-time scrolling and layout timing at the native speed
### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#33065 

### Tested the behavior in the following platforms

- [x] Windows
- [x] Android
- [x] iOS
- [x] Mac

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/1045cbe3-e869-4e08-9562-8032dd9cea88">
| <video
src="https://github.com/user-attachments/assets/0c7b3bbb-4917-482f-ac7e-05e343de3ae0">
|


<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…4936)

## Summary

- For `Default` and `Fixed` `FlyoutHeaderBehavior`, the flyout scroll
view is now positioned below the header instead of overlapping it with a
content inset. This prevents items from rendering behind
semi-transparent headers when scrolling.
- `Scroll` and `CollapseOnScroll` behaviors are unchanged — they still
overlap the header so it can scroll away or shrink.
- Adds regression test
`FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` and updates
`FlyoutHeaderContentAndFooterAllMeasureCorrectly` to match the new
layout for Default/Fixed.

Fixes dotnet#34925

## Changes

**`ShellFlyoutLayoutManager.cs`** (iOS):
- `SetHeaderContentInset()`: For Default/Fixed, sets `ContentInset.Top =
0` since the scroll view frame already starts below the header.
- `LayoutContent()`: For Default/Fixed, adds the full header height to
the content Y offset instead of just the margin.

**`ShellFlyoutTests.cs`**:
- New test: `FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` —
verifies the scroll view frame starts at or below the header bottom for
Default and Fixed behaviors.
- Updated test: `FlyoutHeaderContentAndFooterAllMeasureCorrectly` —
adjusts iOS ScrollView expectations so that only Scroll/CollapseOnScroll
use content inset; Default/Fixed use frame positioning.

## Test plan

- [x] New regression test
`FlyoutScrollViewDoesNotOverlapHeaderForDefaultAndFixed` fails without
fix, passes with fix
- [x] Existing `FlyoutHeaderContentAndFooterAllMeasureCorrectly` test
passes with updated expectations
- [ ] Manual verification: open flyout with semi-transparent header and
15+ items, scroll down — items should not be visible behind header

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------
…t#28071)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!


<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
When a Margin value is set for the Path, it renders correctly. However,
when the Path is placed inside a StackLayout, it does not render.

### Root Cause
While measuring the Path, the size calculation does not account for the
Margin value, which leads to incorrect path rendering.

### Description of Change
To include the Margin value in size calculations and ensure proper Path
rendering.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
 
### Issues Fixed
  
Fixes dotnet#13801 

### Output  ScreenShot

| Before  | After  |
|---------|--------|
| <img width="501" alt="image (2)"
src="https://github.com/user-attachments/assets/7afdacbd-089b-47c2-bad9-216a19618bca"
/> | <img width="494" alt="image (3)"
src="https://github.com/user-attachments/assets/c64f2eeb-b9c0-4340-beb6-f35fd63f28fc"
/> |
…hicsView (dotnet#34557)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
The FlowDirection property is not functioning as expected when applied
to Drawable controls and GraphicsView.
When FlowDirection is set to either RightToLeft or LeftToRight, there is
no observable change in layout behavior.

### Root Cause
ShapeViewHandler had no FlowDirection mapper, and ShapeDrawable.Draw()
never applied canvas mirroring.

### Description of Change
**Android**: Updated PlatformGraphicsView to mirror its content
horizontally when the layout direction is RTL by applying a translation
and scale transformation in the Draw method.
**iOS**: Modified PlatformGraphicsView to check
EffectiveUserInterfaceLayoutDirection and apply a horizontal flip
transformation when in RTL mode.
**Windows**: Changed PlatformGraphicsView to concatenate a scale and
translation transform for RTL flow direction before drawing content.

### Validated the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Issues Fixed:
Fixes dotnet#34402 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/62bec2c7-dbe5-4696-9f78-d2d1b4bcdaec">
|   <video
src="https://github.com/user-attachments/assets/d143819d-b525-455b-adeb-d589171de61e"> 
|
…t#34954)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details

- HEIC images picked via PickPhotosAsync are not displayed — the image
appears blank, whereas other image formats (e.g., JPEG, PNG) display
correctly.

### Root Cause of the issue

- PR [34250](dotnet#34250) moved
CompletedHandler invocation into the DismissViewController completion
callback. This introduced a GC race condition where
PhotoPickerPresentationControllerDelegate.Dispose() fires
tcs.TrySetResult([]) before the async CompletedHandler finishes HEIC
transcoding.
- HEIC is particularly affected because
NSItemProvider.LoadDataRepresentationAsync transcoding is significantly
slower than JPEG/PNG loading, widening the GC window.

### Description of Change
**Race condition prevention:**

* In `PhotoPickerDelegate.DidFinishPicking`, the `Handler` property of
`PhotoPickerPresentationControllerDelegate` is set to `null` before
dismissing the picker to avoid a garbage collection race condition that
could interfere with the async completion handler, especially during
slow operations like HEIC transcoding.
<!-- Enter description of the fix in this section -->

### Issues Fixed
Fixes dotnet#34953

### Tested the behaviour in the following platforms

- [ ] - Windows 
- [ ] - Android
- [x] - iOS
- [ ] - Mac

| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/368192e4-57ea-4732-82df-3e3ca386ab35">
| <video
src="https://github.com/user-attachments/assets/0e3a5066-e092-4461-9199-1730475307d8">
|

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…ion on the second programmatic call (dotnet#34982)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details

The issue occurs when calling the SwipeView.Open() method
programmatically multiple times on iOS and MacCatalyst platforms. The
first call to open swipe items (such as RightItems or BottomItems) works
as expected, but the second call throws a System.ArgumentException with
the message “An item with the same key has already been added.”
 
This behavior is specific to certain swipe directions that rely on
negative offsets (such as right and bottom swipe actions), where the
swipe interaction briefly appears and then resets unexpectedly. As a
result, internal state inconsistencies lead to a crash during subsequent
calls.

### Root Cause

The issue occur because of an incorrect use of Math.Abs on the
_swipeOffset value inside the ProgrammaticallyOpenSwipeItem()method.
This operation removes the negative sign required for specific swipe
directions (such as left or up), causing the offset to become invalid
during layout validation.
 
Due to this invalid offset, the swipe view resets to a closed state
while still retaining previously added entries in the _swipeItems
dictionary. When Open() is called again, the same keys are added again
to the dictionary, resulting in a duplicate key exception.

### Description of Change

The fix involves removing the Math.Abs operation on _swipeOffset to
preserve the correct directional value required for swipe behavior,
ensuring that the swipe state remains consistent after layout
validation.
 
Additionally, a defensive improvement is introduced by clearing the
_swipeItems dictionary before repopulating it in the UpdateSwipeItems()
method. This prevents duplicate key insertion and ensures that the
method behaves safely even if invoked multiple times under unexpected
conditions.

### Issues Fixed
Fixes dotnet#34917

### Validated the behaviour in the following platforms

- [ ] Windows
- [ ] Android
- [x] iOS
- [x] Mac

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/55a131cb-6ccc-4776-80d7-90655651565d">
| <video
src="https://github.com/user-attachments/assets/5efc010e-04ec-4e4d-8729-d43f41d2d6bf">
|
…ng a NonFlyOutPage (dotnet#34839)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:

Title of FlyOutPage is not updating anymore after showing a
NonFlyOutPage in Android Platform.
       
### Root Cause:

When Window.Page is swapped from a FlyoutPage to a plain ContentPage,
the Window.Page setter clears flyoutPage.Parent = null synchronously
before propagating the Window=null event through the element hierarchy.
This means by the time NavigationPage.OnWindowChanged(null) fires to
clean up the toolbar, the check flyoutPage.Parent is IWindow evaluates
to false (Parent is already null). As a result, flyoutPage.Toolbar =
null never executes — the stale, now-disconnected NavigationPageToolbar
remains attached to
FlyoutPage.Toolbar. When FlyoutPage is later restored as Window.Page,
FindMyToolbar() traverses the ancestors, finds the stale toolbar on
FlyoutPage.Toolbar, and returns early — no new connected toolbar is
created. Since the stale toolbar's ToolbarTracker subscriptions were
severed by Disconnect(), it never receives CurrentPage change
notifications, so the title is permanently frozen.

### Description of Change:

Remove the flyoutPage.Parent is IWindow && condition from the guard in
NavigationPage.OnWindowChanged(null) in NavigationPage.cs. The remaining
check flyoutPage.Toolbar == _toolbar is the correct and sufficient
invariant — it ensures we only clear the toolbar that this
NavigationPage created. The Parent is IWindow guard was redundant in the
normal case (when FlyoutPage is the root page, Parent is IWindow), but
fatally incorrect during a Window.Page swap because the ordering
guarantee it depended on didn't hold.

**Tested the behavior in the following platforms:**

- [x] Android
- [ ] Windows
- [x] iOS
- [x] Mac

### Reference:

N/A

### Issues Fixed:

Fixes  dotnet#33615         

### Screenshots
| Before  | After  |
|---------|--------|
| <Video
src="https://github.com/user-attachments/assets/00869428-8cb4-43a8-981b-7ac6b018e184"
Width="300" Height="600"> | <Video
src="https://github.com/user-attachments/assets/81f94d6d-8c25-4d08-a699-4b3db32e76c8"
Width="300" Height="600"> |
…otnet#34970)

<!-- Please keep the note below for people who find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment whether this change resolves your
issue. Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

This pull request addresses the issue where the `DatePicker` control on
MacCatalyst did not correctly raise its `Opened` and `Closed` events.
The changes implement a more robust mechanism for detecting when the
DatePicker is opened and closed, specifically for MacCatalyst, and add
new tests to verify this behavior.
### Description of Change :

**MacCatalyst DatePicker Event Handling Improvements:**

* Added logic to traverse the internal view hierarchy of the compact
`UIDatePicker` to find and wire up `UITextField` subviews, allowing
detection of when the DatePicker is opened via the `EditingDidBegin`
event. A one-shot observer is registered to detect when the picker
popover window closes, ensuring the `Closed` event is raised reliably.
(`DatePickerHandler.MacCatalyst.cs`)
* Implemented cleanup logic to unwire event handlers and remove
observers during disconnect, preventing memory leaks and spurious event
firing. (`DatePickerHandler.MacCatalyst.cs`)
* Removed previous event handler attachments
(`EditingDidBegin`/`EditingDidEnd`) from the proxy, as the new mechanism
supersedes them. (`DatePickerHandler.MacCatalyst.cs`)
[[1]](diffhunk://#diff-2107542f6f788907263db46eab6a80232ed765aa515806945e8f65681c8421d1L105-L113)
[[2]](diffhunk://#diff-2107542f6f788907263db46eab6a80232ed765aa515806945e8f65681c8421d1L125-L136)

**Testing Enhancements:**

* Added a new test case page (`Issue34848`) and a corresponding UI test
to verify that the `Opened` and `Closed` events are raised correctly on
MacCatalyst and other platforms, using platform-specific logic to close
the DatePicker. (`TestCases.HostApp/Issues/Issue34848.cs`,
`TestCases.Shared.Tests/Tests/Issues/Issue34848.cs`)
[[1]](diffhunk://#diff-652f2cd8a1e252cf8db29bf33034066d08ec5e3b43ec76a77d477824a08a1f44R1-R46)
[[2]](diffhunk://#diff-a57ba10bf75c97b2a6f28c075585a1066df8c7c2c31751e3c799177fca6560b4R1-R42)

**General Codebase Improvements:**

* Minor code cleanup and improved organization in the DatePicker handler
for MacCatalyst, including the addition of necessary using directives.
(`DatePickerHandler.MacCatalyst.cs`)


<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#34848 

### Tested the behavior in the following platforms

- [ ] Windows
- [ ] Android
- [ ] iOS
- [x] Mac

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/73c4686f-30d0-4a57-bb8d-be6b2d4b7cda">
| <video
src="https://github.com/user-attachments/assets/65d6f44e-b145-48de-b41a-77b6abefabc4">
|
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
SubhikshaSf4851 and others added 21 commits May 29, 2026 15:56
…5543)

<!-- Please keep the note below for people who find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment whether this change resolves your
issue. Thank you!
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

This pull request addresses a memory leak issue involving
`GradientBrush` subscriptions in the `TabbedPage` renderer/manager. It
ensures that when a `TabbedPage` is removed from the window, any event
subscriptions to a shared `GradientBrush` are properly cleaned up,
preventing the renderer/manager from being kept alive unintentionally.
The changes also add tests to verify this behavior.
### Description of Change
**Memory leak prevention and event unsubscription:**

* The `Dispose` method in `TabbedRenderer.cs` (iOS) and the `SetElement`
method in `TabbedPageManager.cs` (Android) now explicitly unsubscribe
from the `GradientBrush.InvalidateGradientBrushRequested` event and
clear the brush's parent, ensuring no lingering references when the
`TabbedPage` is disconnected.
[[1]](diffhunk://#diff-9fde794f3d6a007e6182c0353ba2136323fb69141c1d880e49a42f9015274a53R157-R163)
[[2]](diffhunk://#diff-c76199129810d626ed8ca8ea05723ccc0f9b73d76d7de83cb353e9cf3a01bbacR132-R138)

**Automated tests for leak prevention:**

* Added two tests to `TabbedPageTests.cs` that verify the
renderer/manager does not leak a shared `GradientBrush` subscription
when a `TabbedPage` is removed, both when the brush is set directly and
when applied via a `Style`. These tests use reflection to check for
remaining event subscribers and assert that none remain after GC.
* Introduced a helper method using reflection to inspect the invocation
list of the `InvalidateGradientBrushRequested` event on a
`GradientBrush` instance.
* Added the necessary `System.Reflection` import to support the new test
utilities.



<!-- Enter description of the fix in this section -->

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#35469 
### Tested the behavior in the following platforms

- [ ] Windows
- [x] Android
- [x] iOS
- [ ] Mac

| Android Before Issue Fix | Android After Issue Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/b421d3bd-be0f-4ec6-8198-fe6d9a43b9f4">
| <video
src="https://github.com/user-attachments/assets/5fcc7cc5-078b-4975-abb0-5236f89d17d3">
|

| iOS Before Issue Fix | iOS After Issue Fix |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/9150410d-f53b-4a3c-914c-611744535d75">
| <video
src="https://github.com/user-attachments/assets/3aba2204-8272-4bdf-8553-baf92158812d">
|
<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->
…ions (dotnet#34992)

## Description

The `.github/instructions/integration-tests.instructions.md` file
contained incorrect build commands:

```bash
./build.sh --target=dotnet
./build.sh --target=dotnet-local-workloads
```

These mix up Arcade build script syntax (`./build.sh`) with Cake target
syntax (`--target=X`). Unrecognized arguments are silently passed
through to MSBuild as properties via `eng/common/build.sh` (line
200-201) and do nothing useful.

## Fix

Replace with `dotnet cake`

```bash
dotnet cake --target=dotnet
dotnet cake --target=dotnet-local-workloads
```

## Impact

Documentation-only change. No functional code changes.

AI now uses the right commands.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…and Dropdown Items (dotnet#30612)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details

- On Windows, setting the CharacterSpacing property on a Picker control
has no visual effect — the spacing is not applied to either the title
text or the individual items in the dropdown.

### Root Cause

- On Windows, the Picker control is rendered using a native ComboBox,
which does not automatically apply the CharacterSpacing property to the
title or the items in the dropdown list.
- Additionally, applying CharacterSpacing directly to the ComboBox
control does not affect the internal TextBlock used to render the
selected item or the item templates.

### Description of Change

- Added a new CharacterSpacingConverter class to convert
CharacterSpacing values to the correct format for UWP rendering.
- Enhanced the UpdateCharacterSpacing method to apply character spacing
to the selected item in the ComboBox, including handling scenarios where
the control is not yet loaded.
- Updated the ComboBoxHeader template to use the new
CharacterSpacingConverter for binding character spacing values, and
registered the converter as a static resource for use in XAML.

### Issues Fixed
Fixes dotnet#30464 

### Validated the behaviour in the following platforms

- [x] Windows
- [ ] Android
- [ ] iOS
- [ ] Mac

### Output
| Before | After |
|----------|----------|
| <video
src="https://github.com/user-attachments/assets/fb173981-30a9-4bef-804b-e1d51431dcd3">
| <video
src="https://github.com/user-attachments/assets/3d1c1ac4-bf52-4fed-9f70-0028b08631cc">
|
…perties.Hint or TapGestureRecognizer (dotnet#35590)

> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details
On iOS and MacCatalyst, when a container layout (for example,
VerticalStackLayout) has SemanticProperties.Hint set, with or without a
TapGestureRecognizer, VoiceOver focuses the entire layout as a single
accessibility element but reads only the Hint, ignoring all child label
text.
On Android, TalkBack handles the same layout correctly by reading the
child text followed by the hint.

### Root Cause
iOS accessibility uses a flat accessibility model. A UIView can either
behave as:

- a leaf accessibility element, where VoiceOver reads the view’s
AccessibilityLabel and AccessibilityHint while hiding its children, or
- a container, where child elements are read individually but the
container’s own label and hint are ignored.

It cannot behave as both at the same time. The previous MAUI
implementation in SemanticExtensions.UpdateSemantics marked layouts with
Hint or Description as IsAccessibilityElement = true and assigned
AccessibilityHint = Hint, but never populated AccessibilityLabel. As a
result, VoiceOver read only the hint, while all child content was hidden
because the layout became a leaf accessibility element.

**Why Android is unaffected**
Android accessibility uses a hierarchical accessibility model. TalkBack
walks the native view tree and reads both parent and child accessibility
content together.
For example, on a VerticalStackLayout with Hint = "more info" containing
two labels, TalkBack naturally announces:
"[label1], [label2], more info". There is no parent/child exclusivity
like on iOS. The MAUI Android implementation sets accessibility
properties such as ContentDescription and ImportantForAccessibility per
view, and TalkBack correctly combines them while traversing the tree.

### Description of Change
**SemanticExtensions.cs:** For ILayout views with Hint set, the fix now
populates AccessibilityLabel before promoting the layout to an
accessibility element. If Description is provided, it is used directly
as the label. Otherwise, the label is synthesized from child IText
content. This allows VoiceOver to read both the layout content and the
hint together as a single focus unit.
The fix also resets IsAccessibilityElement back to false when both Hint
and Description are later cleared from the layout.
**GesturePlatformManager.iOS.cs:** The existing
ShouldGroupAccessibilityChildren value is now captured before
modification and restored during cleanup.
Also added an AccessibilityActivateCallback registration on MauiView to
improve MacCatalyst Ctrl+Option+Space tap reliability, since UIKit’s
default activation path is unreliable on Catalyst.
**MauiView.cs:** Added AccessibilityActivateCallback : Func<bool>? and
overrode AccessibilityActivate() so the callback can invoke
TapGestureRecognizer.SendTapped() directly.

### Issues Fixed
Fixes dotnet#34380

### Screenshots

**iOS:**
| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/3e45feaa-ef89-4e20-b29b-20618ea7770e">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/092db6da-9ce6-48d1-a8b3-902f403e8c75">
|

**Mac:**
| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/43f4e5bc-82a6-40e7-89c6-542f5a246fc2">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/13c213c5-22d1-4bfe-8252-a6a015e358fc">
|
…in test projects (dotnet#29905)

Also fixes an issue uncovered by the new version where the cake script
previously launched packaged DeviceTests apps via`Start-Process
shell:AppsFolder\<PFN>!App` and then polled for the result XML's
existence. That launcher process exits immediately while the actual app
keeps running, so cake had no signal for "app done" —
it had to fall back to FileExists, which fires the moment xharness
creates the (still-open) results file at byte zero in the new version.

Replace this with `IApplicationActivationManager::ActivateApplication`,
which returns the real PID of the launched packaged app. The cake task
then blocks on `Process.WaitForExit`, guaranteeing the results file
handle is closed and the file is complete before we move on. This
removes the polling loop, kills the file-handle race against
`Application.Current.Exit()`, and matches what XHarness / WinAppDriver
do internally.

---------
### Description of Change

Fixes `Picker.SelectedIndex` initialization when `SelectedIndex` is set
before picker items are available.

The requested index is now preserved while the picker has no items, the
public `SelectedIndex` remains coerced to `-1` until a valid item list
exists, and the pending value is applied once items are loaded. Deferred
initialization updates `SelectedItem` but does not raise
`SelectedIndexChanged`.

Adds focused core, XAML, and UI regression coverage for `ItemsSource`
and inline `Picker.Items` initialization paths.

### Issues Fixed

Fixes dotnet#9150

---------
…solveActivity pre-check (dotnet#35652)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Description of Change

Removes the `PlatformUtils.IsIntentSupported(intent)` pre-check from
`Browser.OpenAsync(uri, BrowserLaunchMode.External)` on Android, and
instead relies on `ActivityNotFoundException` from
`Application.Context.StartActivity` as the authoritative "no handler"
signal.

**Why the pre-check is wrong**

`PlatformUtils.IsIntentSupported` calls `Intent.ResolveActivity(pm)`. On
Android 11+ (API 30, package visibility), that call returns `null` in
**two distinct cases**:

1. No activity exists that can handle the intent. `StartActivity` would
also fail.
2. An activity exists but is **invisible** to the caller because of
`<queries>` package visibility filtering. `StartActivity` would still
succeed — system intent dispatch is not subject to caller-side
visibility, only `query*` APIs are.

Today MAUI conflates the two and throws `FeatureNotSupportedException`
even in case 2, blocking a launch Android could perform.

This breaks the common case of opening a URL whose owner is a **verified
App Link** that the caller has not explicitly declared as visible
(Instagram, Facebook, Spotify, X, TikTok, Google Maps, etc.). The
standard `<queries><intent VIEW + scheme=https></intent></queries>`
declaration recommended in the docs grants visibility to **generic
browsers** (whose VIEW filter is host-less) but **not** to host-bound
App Link owners — per [Android's auto-visibility
rules](https://developer.android.com/training/package-visibility/automatic#web-intents):

> "If the intent filter includes a `<data>` element that contains a
host, then your app is NOT considered to handle a web intent."

So even with the documented manifest fix, the App Link owner case stays
broken.

**The fix**

```csharp
try
{
    Application.Context.StartActivity(intent);
}
catch (ActivityNotFoundException ex)
{
    throw new FeatureNotSupportedException(
        "No activity found to handle URI: " + nativeUri, ex);
}
```

`ActivityNotFoundException` is the only authoritative signal that no
activity can actually handle the intent, since only the system
dispatcher knows. The public contract (`FeatureNotSupportedException`
thrown when no activity is available) is preserved — we just wrap the
real Android exception instead of guessing from a visibility-filtered
query.

This matches @jfversluis's own suggestion on the related issue dotnet#27744:

> *"Yeah looks like we check if the intent is supported for a URL. I
guess if its http(s) we should just open the browser and not do anything
further."*

A more conservative variant (skip the pre-check only for `http`/`https`,
keep it for custom schemes) is described in the linked issue as Option
B; happy to switch if reviewers prefer it.

**Testing**

Manually verified on a Pixel running Android 14:
- Before the change:
`Browser.OpenAsync("https://www.instagram.com/instagram/", External)`
with Instagram installed throws `FeatureNotSupportedException`.
Confirmed `AppsFilter: ... BLOCKED` in logcat and confirmed Instagram is
missing from the calling app's `dumpsys package queries` visible list
despite a correct `<queries>` `http`/`https` block.
- After the change: same call opens the Instagram app directly via App
Link routing.
- Cross-checked behavior is unchanged for: generic URL (no installed App
Link owner) → opens default browser; non-web custom scheme intent with
no handler → still throws `FeatureNotSupportedException`, now wrapping
the underlying `ActivityNotFoundException`.

No existing Android-platform tests for `Browser.OpenAsync` to update
(`Browser_Tests.cs` covers only the netstandard reference assembly and
URI escaping). Happy to add device-level tests if maintainers want them
as a follow-up — the test infra change is larger than this fix.

### Issues Fixed

Fixes dotnet#35651

---------
…zeCollectItems (dotnet#35575)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

## Description

Fixes dotnet#35574

In `Microsoft.Maui.Resizetizer.After.targets`, the
`ResizetizeCollectItems` target calls `GetMauiItems` on referenced
projects but only passes `TargetFramework` — it does NOT propagate
`AdditionalProperties` from the `ProjectReference` items. This means any
conditional item removal in the referenced project (like stripping
`MauiIcon` when `IsTestLibrary=true`) is ignored during Resizetizer's
collection, causing duplicate filename errors.

## Changes

### Fix (`Microsoft.Maui.Resizetizer.After.targets`)
1. Added an `Update` step that copies `AdditionalProperties` metadata
from matching `ProjectReference` items to
`_ResizetizeCollectItemsProject`
2. Appended `%(_ResizetizeCollectItemsProject.AdditionalProperties)` to
the `Properties` parameter in the `<MSBuild>` task call

### Tests (`ResizetizerTests.cs`)
Two new integration tests validate the fix:
- **`AdditionalPropertiesExcludesImage`** — Verifies that setting
`ExcludeLibraryImage=true` via `AdditionalProperties` causes the library
to conditionally remove its `MauiImage`, preventing it from being
collected by the app
- **`AdditionalPropertiesPassesColorToLibrary`** — Verifies that a
`Color` property override flows through `AdditionalProperties` and is
used when processing the library image

---------
<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

Updates the Windows App SDK (WinAppSDK) package version from
`1.8.251106002` to `1.8.260508005`.

This replaces dotnet#33850 with the latest listed stable 1.8 servicing build
available on NuGet.

## Changes Made

- Updated `MicrosoftWindowsAppSDKPackageVersion` in
`eng/Versions.props`.

---------
…otnet#35421)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:
When a custom control overrides ChangeVisualState() and applies the
Selected state manually, calling base.ChangeVisualState()after the
control is deselected causes the element to remain permanently stuck in
the Selected visual state.

### Root Cause:
In .NET 10.0.60, a fix for dotnet#29815 modified ChangeVisualState() to check
whether the element is currently in the "Selected" VSM state before
transitioning to PointerOver or Normal. This check was implemented by
reading VisualStateGroup.CurrentState?.Name via
IsElementInSelectedState() — creating a circular dependency.

During a deselect, CurrentState is still "Selected" (it hasn't been
cleared yet when ChangeVisualState() calls IsElementInSelectedState()).
So isSelected = true → "Selected" is re-applied → element is stuck.


### Description of change:

- VisualElement.IsItemSelected (internal property) — has an equality
guard to avoid redundant recomputation and routes through
ChangeVisualState() so that state priorities (Disabled > Selected >
PointerOver > Normal) are always respected.
- VisualStateManager.IsElementInSelectedState() — now simply returns
element.IsItemSelected instead of reading CurrentState.

### Validated the behaviour in the following platforms
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

### Fixes
Fixes dotnet#35399 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/0fe16315-5561-4b08-92bc-094b673579a9">
|   <video
src="https://github.com/user-attachments/assets/52a0bc40-04f3-4e1f-b314-9726ae883f08"> 
|
<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Description of Change

In order to provide a good reference, I repeated the flow twice and
collect the results. With the `main`, we do have this memory footprint:

<img width="1436" height="138" alt="image"
src="https://github.com/user-attachments/assets/f0b57463-7a8f-4767-9f55-301d5696d4bf"
/>


Those `System.Action` and `DisplayClass` allocations are caused by the
usage of `ForEach` method, they fancy but they come with a cost. Since
they're inside a loop the compiler couldn't cache them (my theory) which
causes a lot of allocations.

To remove all those allocations I rewrite the code using the boring
`foreach` loop. And you can see the memory footprint below:

<img width="1440" height="113" alt="image"
src="https://github.com/user-attachments/assets/e9ab701b-29c6-42cc-9e86-906df4cc503e"
/>

Where the only allocation now is related with the `List` that is created
inside the method and I wasn't able to remove on this PR. I'll let it
for the future me or contributors.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#35654

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

From IA review:

<img width="658" height="198" alt="image"
src="https://github.com/user-attachments/assets/44b9be3a-c6ee-4ae9-a5d0-2bb5d9bcd603"
/>


The 19 → 26 difference is just sampling noise. GCAllocationTick fires
every ~100 KB of allocations, not on every individual allocation. The
improved run had 2,017 sampled events vs 2,000 in the baseline (17
more), which means a slightly different sampling window was captured.
That's enough to shift counts on any type by a few ticks.

What actually changed:

✅ Action<Animation> (30 → 0): gone — the ForEach(delegate) no longer
creates a delegate on each call
✅ <>c__DisplayClass20_0 (? → 0): the compiler-generated closure class is
gone too
➡️ Animation[] (19 → 26): same root cause (new List<Animation> backing
array), just sampling variance

---------
…tforms (dotnet#35632)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
SwipeItem automatically applies a tint/filter to the icon when a
background color is set. Because of that, a custom red icon can change
to white/black instead of keeping its original color.


### Root Cause
Android/iOS applied tint universally to all drawable types instead of
only FontImageSource; Windows used synchronous conversion that
rasterized font glyphs instead of preserving vector rendering.


### Description of Change
Added platform-specific source-type checking: Android/iOS apply tint
only to FontImageSource with fallback to item text color; Windows uses
async LoadFileIconAsync to load all sources uniformly through image
services, preserving font vector quality.

### Behavior change 

Currently, the icon color is always determined based on the background
color, regardless of whether the icon is a PNG, SVG, or FontImageSource.
 
With the changes in this PR, the behavior will be as follows:

- If a PNG or SVG image is provided, no color changes will be applied.
The image will be displayed using its original colors.
- If a FontImageSource is provided with an explicit color, the specified
color will be used.
- If a FontImageSource is provided without a color, the existing
behavior will be retained.

|Scenario|Before|After|
|--|--|--|
| Png image with background color| <img width="718" height="1466"
alt="image"
src="https://github.com/user-attachments/assets/271c180c-444e-4919-b721-985f0a58f7e0"
/> | <img width="718" height="1466" alt="image"
src="https://github.com/user-attachments/assets/baf47387-c54a-4332-a6c4-141fb73ab327"
/>|
| Svg image with background color| <img width="718" height="1466"
alt="image"
src="https://github.com/user-attachments/assets/61bd8b0f-462c-4461-8a18-033e45d497d5"
/>| <img width="718" height="1466" alt="image"
src="https://github.com/user-attachments/assets/d9129899-4560-4450-946f-d820d1f6820e"
/>|
|FontImage with color | <img width="718" height="1466" alt="image"
src="https://github.com/user-attachments/assets/2afd4b8a-f276-4c56-92b2-0449301d39e1"
/>| <img width="718" height="1466" alt="image"
src="https://github.com/user-attachments/assets/1991dce7-f767-4616-a2f1-9e7a35e4c3e9"
/>|
| FontImage with without color|<img width="718" height="1466"
alt="image"
src="https://github.com/user-attachments/assets/0b07e2fa-a3e1-4be9-b0be-7dad53d823c3"
/> | <img width="718" height="1466" alt="image"
src="https://github.com/user-attachments/assets/ec5da308-87a3-48b0-968a-bb68df4b9686"
/>|

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#23074 

### Output  ScreenShot

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/4e335c3a-bcbc-489d-a040-d8f375b82015"
>| <video
src="https://github.com/user-attachments/assets/60206583-184f-4ac5-8e31-08cfcd7ac512">|

---------
…to maintain the scroll position when the loop value is changed at runtime (dotnet#29527)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Root cause
Loop property was not mapped for Android CarouselView, causing loop
behavior to not work. Scroll position was also not maintained on loop
value change.

### Description of Change

Mapped the Loop property, added logic to maintain the current scroll
position when the Loop value is updated., and hid scrollbar when loop is
true.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#29411 
Fixes dotnet#29449 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

Note: Bhavanesh's [PR](dotnet#29453) also
addresses the same issue. However, this PR includes additional logic to
maintain the current item when the loop value changes and also handles
scrollbar visibility based on the loop value.

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **Android**<br> <video
src="https://github.com/user-attachments/assets/95989e6d-fbc3-47d3-8bae-70176fc5c8b1"
width="300" height="600"> | **Android**<br> <video
src="https://github.com/user-attachments/assets/569b6b6e-f633-45d4-9f81-a212dd29cb40"
width="300" height="600"> |

---------

Co-authored-by: Vignesh-SF3580 <102575140+Vignesh-SF3580@users.noreply.github.com>
…sGrouped="True" (dotnet#35609)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Issue Details:

When a CollectionView has IsGrouped="True", calling ScrollTo(index)
should scroll to the item at that position. This works correctly on
Android, but on iOS, MacCatalyst, and Windows scrolled not at all.

### Root Cause:

- When ScrollTo(index) is called with no explicit group (GroupIndex ==
-1), both iOS paths fell through to:
  return NSIndexPath.Create(0, args.Index); // always section 0
- For a grouped CollectionView with multiple sections, any flat index
that exceeded the count of section 0 produced an invalid NSIndexPath —
UICollectionView.ScrollToItem silently did nothing.
- Additionally, IsIndexPathValid in the Items1 handler
(ItemsViewHandler.iOS.cs) lacked a null guard. Since
ConvertFlatIndexToGroupedIndexPath returns null for out-of-range
indices, this could cause a NullReferenceException.

### Description of change:

- Added ConvertFlatIndexToGroupedIndexPath to both
ItemsViewHandler.iOS.cs and ItemsViewHandler2.iOS.cs :
- Activated only when groupable.IsGrouped == true.
- Bounds-checks flat index against itemsSource.ItemCount before walking
groups
- Iterates IItemsViewSource.ItemCountInGroup(section) to resolve the
NSIndexPath(section, item). Returns null for out-of-range indices
(safely handled by
- Also added indexPath is null || guard to IsIndexPathValid (Items1) to
NullReferenceException on out-of-range scroll requests.

### Validated the behaviour in the following platforms
- [ ] Android
- [ ] Windows
- [x] iOS
- [x] Mac

### Fixes
Fixes dotnet#35326 

### Screenshots
| Before  | After |
|---------|--------|
| <video
src="https://github.com/user-attachments/assets/846e050f-c2fa-4bf1-a952-789c9e8466ea">
| <video
src="https://github.com/user-attachments/assets/d379b495-6fbc-47f1-8e57-36fc472a81ed">
|
### Description of Change

This PR reduces Android `CollectionView`/`CarouselView` item allocation
overhead from SafeArea inset listener attachment.

The Android SafeArea implementation currently attaches inset listeners
to item views under `IMauiRecyclerView`, even when those item views have
default `SafeAreaEdges` behavior. This is expensive during item
realization/recycling and materially increases allocations and GC
pressure in scroll-heavy CollectionView scenarios.

This change gates Android inset listener attachment for descendants of
`IMauiRecyclerView` so listeners are only attached when the target
platform view backs an `ISafeAreaView2` with explicitly configured
`SafeAreaEdges`.

The fix also preserves behavior for dynamic SafeArea changes:

- default item views do not attach inset listeners;
- explicit `SafeAreaEdges` values, including `SafeAreaEdges.None`,
remain eligible;
- changing from default to explicit refreshes and attaches the listener;
- clearing/changing back to default removes the listener and resets
applied padding;
- existing `AppBarLayout`, `MaterialToolbar`, and `MauiScrollView`
exclusions are preserved.

Validation completed:

- `dotnet test
src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj
--filter FullyQualifiedName~SafeAreaTests --nologo`
- Android device test build and CollectionView SafeArea listener tests
- MauiAppGC repro, Release Android, explicit APK install, `Reset -> End
-> Start -> End`, 5 repeats:
  - `10.0.70`: ~270 MB allocated, `65/2/2` GCs
  - SafeArea gating patch: ~151 MB allocated, `38/0/0` GCs

### Issues Fixed

Fixes dotnet#35344

---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
…nt (dotnet#35625)

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue details:
A regression was introduced by PR
#[34521](dotnet#34521) on Android. It set
RadioButton accessibility metadata (ClassName, Checkable, Checked) on
AccessibilityNodeInfoCompat during TalkBack. The bool setter for Checked
no longer exists in AndroidX Core 1.17.x (API changed), causing a
MissingMethodException at runtime. This mainly affects .NET 10 MAUI
packages that compile against AndroidX Core 1.16.0.3.

### Description of changes:
Fully reverts PR #[34521](dotnet#34521).
Removes the IRadioButton accessibility block (ClassName, Checkable,
Checked) and the cached s_radioButtonClassName field from
SemanticExtensions.cs. Reverts RadioButton.UpdateSemantics back to using
ContentAsString() and removes the GetSemanticDescriptionFromContent()
and TryGetSemanticDescription() helpers. Also removes the two device
tests added for issue
#[34322](dotnet#34322) and their helper
methods.


### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes dotnet#35584 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com>
### Description of Change

Fixes `DatePicker.Focus()` on MacCatalyst so programmatic focus
correctly updates the virtual view state.

On MacCatalyst, `UIDatePicker.BecomeFirstResponder()` can return
`false`, which prevented the MAUI focus request from completing
successfully and left `IsFocused` / `IsOpen` out of sync. This change
adds MacCatalyst-specific command handling for `IView.Focus` and
`IView.Unfocus`, explicitly synchronizes `IsFocused` and `IsOpen`, and
keeps native/user interaction callbacks aligned with the same state.

A MacCatalyst-only regression test was added to verify that a
`DatePicker` can be focused, unfocused, and focused again successfully.

Validation:
- Built `Core.DeviceTests` for `net10.0-maccatalyst`.
- Ran focused MacCatalyst DatePicker device tests.
- Verified the issue repro changes from reproduced to not reproduced
with the local fix.
- Verified iOS remains not reproduced.

### Issues Fixed

Fixes dotnet#5947

---------
…oid (dotnet#35563)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details
Button TextColor is set at runtime (for example, DarkRed) and then reset
to null.
Expected behavior: the button text should return to the platform default
color.

### Root Cause
Setting TextColor applies native foreground/title overrides. When
TextColor was reset to null, some handlers did not actively clear those
native overrides.

### Description of Change
Android : Saved each control’s original TextColors once and reused it
when TextColor is null, restoring the real platform default.

iOS : Updated iOS button text color handling in
[ButtonExtensions.cs](vscode-file://vscode-app/Applications/Visual%20Studio%20Code.app/Contents/Resources/app/out/vs/code/electron-browser/workbench/workbench.html)
to clear overridden title colors when TextColor is null.

Validated the behavior in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed
  
Fixes dotnet#35513

### Output  ScreenShot
Android

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/9728c36a-5b0d-4061-8190-fc550e65d128"
>| <video
src="https://github.com/user-attachments/assets/5a3470c6-b663-4ae6-9d88-5222102a455b">|

iOS

|Before|After|
|--|--|
| <video
src="https://github.com/user-attachments/assets/bfd4f513-9bf0-4f25-8151-621a593c5d1f"
>| <video
src="https://github.com/user-attachments/assets/faebe1b3-c01a-4906-8a9b-7da581dec527">|

---------
### Issue Details:
Placing a BoxView inside a Border results in incorrect sizing.

### Root Cause
On Windows, when content inside a Border has the same or larger explicit
size than the Border itself, the WinUI AdjustForExplicitSize logic
expands the content's measured size back to its requested dimensions,
even after the available size has been reduced to account for the border
stroke. This causes MeasureContent to return a desired size larger than
the Border by StrokeThickness * 2, resulting in the parent layout
allocating an oversized layout slot. Consequently, the right and bottom
border strokes can be clipped during rendering.

### Description of Change
Override MeasureOverride in the Windows ContentPanel to constrain the
reported desired size to the Border's explicit Width and Height when
both values are set. This ensures the correct desired size is returned
to the parent layout, allowing the Border to receive the intended layout
slot size while preserving the existing ArrangeOverride and clipping
behavior.

### Validated the behaviour in the following platforms
 
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac
 
### Issues Fixed:
Fixes dotnet#19668 
 
### Screenshots
| Before  | After  |
|---------|--------|
| <img
src="https://github.com/user-attachments/assets/b81f0982-e3c2-40d9-ab8b-0b1d743fa99a">
| <img
src="https://github.com/user-attachments/assets/f45e3ebf-e48c-48dd-9c39-42c5413c9268">
|
### Description of Change

Fixes Android touch arbitration for RecyclerView-backed MAUI items
controls when nested inside a perpendicular parent scroll container.

A horizontal `CarouselView` or `CollectionView` inside a vertical
`ScrollView` now releases vertical gestures to the nearest scrollable
parent instead of continuing to hold the gesture in the RecyclerView.
Own-axis gestures are still handled by the RecyclerView, and existing
disabled `ItemsView` behavior plus `CarouselView.IsSwipeEnabled`
behavior are preserved.

Adds an Android Appium regression page and test for the nested
`CarouselView` scenario. The test verifies that vertical scrolling from
inside the carousel works before and after a separate horizontal scroll
gesture.

### Issues Fixed

Fixes dotnet#7814

---------
…ze to preserve WebView2 initialization gate

Before dotnet#32491, InvokeJavaScriptAsync routed through handler.InvokeAsync which
eventually reached RunAfterInitialize on Windows. After dotnet#32491, the shared
helper called handler.PlatformView.EvaluateJavaScript directly, bypassing the
WebView2 initialization check.

This fix restores the RunAfterInitialize gate on Windows for both
EvaluateJavaScriptAsync and InvokeJavaScriptAsync by using #if WINDOWS guards
to route through the handler command mapper instead of direct platform view calls.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35702

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35702"

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🔍 Skill Validation Results

✅ Static Checks Passed

Skills checked: 18 | Agents checked: 4

Full validator output
Found 2 skill(s)
[agentic-labeler] 📊 agentic-labeler: 2,839 BPE tokens [chars/4: 2,788] (standard ~), 8 sections, 0 code blocks
[agentic-labeler]    ⚠  Skill is 2,839 BPE tokens (chars/4 estimate: 2,788) — approaching "comprehensive" range where gains diminish.
[agentic-labeler]    ⚠  No code blocks — agents perform better with concrete snippets and commands.
[find-regression-risk] 📊 find-regression-risk: 967 BPE tokens [chars/4: 905] (detailed ✓), 10 sections, 2 code blocks
[find-regression-risk]    ⚠  No YAML frontmatter — agents use name/description for skill discovery.
✅ All checks passed (2 skill(s))
Found 4 agent(s)
Validated 4 agent(s)

✅ All checks passed (4 agent(s))

⏭️ LLM Evaluation: Skipped

💡 LLM evaluation was not run for this external PR.
A repository contributor can post /evaluate-skills on this PR to trigger full evaluation.

🔍 Full results and investigation steps

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Jun 2, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Hey there @@ne0rrmatrix! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Hey there @ne0rrmatrix! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@ne0rrmatrix ne0rrmatrix closed this Jun 2, 2026
@ne0rrmatrix ne0rrmatrix deleted the fix/hybridwebview-windows-init-regression branch June 2, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-hybridwebview HybridWebView control community ✨ Community Contribution platform/windows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[inflight regression] Windows HybridWebView JS invocation bypasses WebView2 initialization after #32491