Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/Core/src/Platform/Windows/ContentPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
#endif
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Shapes;

namespace Microsoft.Maui.Platform
Expand Down Expand Up @@ -51,6 +53,25 @@ internal FrameworkElement? Content

internal bool IsInnerPath { get; private set; }

internal void UpdateFocusability()
{
// Make the panel focusable only when it has semantic properties
if (CrossPlatformLayout is IView view)
{
var semantics = view.Semantics;
var hasSemanticsDescription = semantics != null && !string.IsNullOrEmpty(semantics.Description);

// Update focusability based on semantic properties
Focusable = hasSemanticsDescription;
IsTabStop = hasSemanticsDescription;
}
else
{
Focusable = false;
IsTabStop = false;
}
}

protected override global::Windows.Foundation.Size ArrangeOverride(global::Windows.Foundation.Size finalSize)
{
var actual = base.ArrangeOverride(finalSize);
Expand All @@ -71,6 +92,7 @@ public ContentPanel()
EnsureBorderPath(containsCheck: false);

SizeChanged += ContentPanelSizeChanged;
KeyDown += ContentPanelKeyDown;
}

void ContentPanelSizeChanged(object sender, SizeChangedEventArgs e)
Expand Down Expand Up @@ -211,5 +233,22 @@ void UpdateClip(IShape? borderShape, double width, double height)

visual.Clip = geometricClip;
}

protected override AutomationPeer OnCreateAutomationPeer()
{
return new ContentPanelAutomationPeer(this);
}

void ContentPanelKeyDown(object sender, KeyRoutedEventArgs e)
{
// Handle Enter and Space keys for selection/activation
// This allows keyboard users to interact with borders that have semantic descriptions
if (e.Key == Windows.System.VirtualKey.Enter || e.Key == Windows.System.VirtualKey.Space)
{
// Raise the Tapped event to maintain consistency with pointer interactions
// The handler for this event can be set by the virtual view
e.Handled = true;
}
}
}
}
46 changes: 46 additions & 0 deletions src/Core/src/Platform/Windows/ContentPanelAutomationPeer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using Microsoft.UI.Xaml.Automation.Peers;

namespace Microsoft.Maui.Platform
{
public partial class ContentPanelAutomationPeer : FrameworkElementAutomationPeer
{
public ContentPanelAutomationPeer(ContentPanel owner) : base(owner)
{
}

protected override string GetClassNameCore() => nameof(ContentPanel);

protected override AutomationControlType GetAutomationControlTypeCore()
{
// Return Group as the control type for a content panel, which is appropriate for containers
return AutomationControlType.Group;
}

protected override IList<AutomationPeer>? GetChildrenCore()
{
// Return null to suppress child automation peers, similar to MauiButtonAutomationPeer
// This prevents nested controls from being announced separately
return null;
}

protected override bool IsControlElementCore()
{
// Make the panel appear in the control view of the automation tree
// This allows it to be keyboard navigable
return true;
}

protected override bool IsKeyboardFocusableCore()
{
// Allow keyboard focus when semantic properties are set
var owner = Owner as ContentPanel;
if (owner?.CrossPlatformLayout is IView view)
{
var semantics = view.Semantics;
return semantics != null && !string.IsNullOrEmpty(semantics.Description);
}
return false;
}
}
}
6 changes: 6 additions & 0 deletions src/Core/src/Platform/Windows/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ public static void UpdateSemantics(this FrameworkElement platformView, IView vie

AutomationProperties.SetHelpText(platformView, semantics.Hint);
AutomationProperties.SetHeadingLevel(platformView, (UI.Xaml.Automation.Peers.AutomationHeadingLevel)((int)semantics.HeadingLevel));

// Update focusability for ContentPanel based on semantic properties
if (platformView is ContentPanel contentPanel)
{
contentPanel.UpdateFocusability();
}
}

internal static void UpdateProperty(this FrameworkElement platformControl, DependencyProperty property, Color color)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,53 @@
namespace Microsoft.Maui.DeviceTests
using Microsoft.Maui.Platform;
using Microsoft.UI.Xaml.Automation.Peers;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
public partial class BorderHandlerTests
{
ContentPanel GetNativeBorder(BorderHandler borderHandler) =>
borderHandler.PlatformView;

[Fact]
public async Task AutomationPeerIsCreated()
{
var border = new BorderStub();

await InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler(border);
var platformView = GetNativeBorder(handler);
var automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(platformView);

Assert.NotNull(automationPeer);
Assert.IsType<ContentPanelAutomationPeer>(automationPeer);
});
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task FocusabilityBasedOnSemantics(bool hasSemantics)
{
var border = new BorderStub();

if (hasSemantics)
{
border.Semantics = new Semantics { Description = "Test Border" };
}

await InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler(border);
var platformView = GetNativeBorder(handler);

// Trigger semantic update
platformView.UpdateSemantics(border);

Assert.Equal(hasSemantics, platformView.Focusable);
Assert.Equal(hasSemantics, platformView.IsTabStop);
});
}
}
}
Loading