diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems
index f3f54971c7c6..78c56312c6f3 100644
--- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems
+++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems
@@ -982,6 +982,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -5980,6 +5984,9 @@
FocusManager_GetFocus_Automated.xaml
+
+ Focus_WASM_Tab.xaml
+
Focus_FocusCycle.xaml
diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml
new file mode 100644
index 000000000000..063a2625586b
--- /dev/null
+++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml
@@ -0,0 +1,24 @@
+
+
+
+ Focus the address bar then tab into the page. SplitView should be focused and not RootVisual.
+
+
+
+
+
+
+
+
+
diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml.cs
new file mode 100644
index 000000000000..93eb9c174f1a
--- /dev/null
+++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml.cs
@@ -0,0 +1,22 @@
+using Uno.UI.Samples.Controls;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Uno.UI.Samples.Content.UITests.FocusTests
+{
+ [Sample("Focus", IsManualTest = true)]
+ public sealed partial class Focus_WASM_Tab : UserControl
+ {
+ public Focus_WASM_Tab()
+ {
+ this.InitializeComponent();
+
+ Microsoft.UI.Xaml.Input.FocusManager.GotFocus += FocusManager_GotFocus;
+ }
+
+ private void FocusManager_GotFocus(object sender, Microsoft.UI.Xaml.Input.FocusManagerGotFocusEventArgs e)
+ {
+ this.TxtCurrentFocused.Text = (e.NewFocusedElement as FrameworkElement)?.Name ?? "";
+ }
+ }
+}
diff --git a/src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs b/src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs
index 4af5fbc6c0d9..18b3be06a96e 100644
--- a/src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs
+++ b/src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using Uno;
using Uno.Foundation;
@@ -11,6 +12,8 @@
using Uno.UI.Xaml.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Uno.Disposables;
+using Uno.UI.Extensions;
namespace Microsoft.UI.Xaml.Input
{
@@ -44,10 +47,30 @@ internal static void ProcessElementFocused(UIElement element)
_log.Value.LogDebug($"{nameof(ProcessElementFocused)}() focusedElement={GetFocusedElement()}, element={element}, searching for focusable parent control");
}
- foreach (var parent in element.GetParents())
+ foreach (var candidate in element.GetAllParents())
{
// Try to find the first focusable parent and set it as focused, otherwise just keep it for reference (GetFocusedElement())
- if (parent is TextBlock textBlock && textBlock.IsFocusable)
+
+ // Special handling for RootVisual - which is not focusable on managed side
+ // but is focusable on native side. The purpose of this trick is to allow
+ // us to recognize, that the page was focused by tabbing from the address bar
+ // and focusing the first focusable element on the page instead.
+ if (candidate is RootVisual rootVisual)
+ {
+ var firstFocusable = FocusManager.FindFirstFocusableElement(rootVisual);
+ if (firstFocusable is FrameworkElement frameworkElement)
+ {
+ if (_log.Value.IsEnabled(LogLevel.Debug))
+ {
+ _log.Value.LogDebug(
+ $"Root visual focused - caused by browser keyboard navigation to the page, " +
+ $"moving focus to actual first focusable element - {frameworkElement?.ToString() ?? "[null]"}.");
+ }
+ frameworkElement.Focus(FocusState.Keyboard);
+ }
+ break;
+ }
+ else if (candidate is TextBlock textBlock && textBlock.IsFocusable)
{
// Focusable TextBlock parent, we can move focus to it.
var focusManager = VisualTree.GetFocusManagerForElement(textBlock);
@@ -58,14 +81,23 @@ internal static void ProcessElementFocused(UIElement element)
_skipNativeFocus = false;
break;
}
+ else if (candidate is TextBoxView textBoxView)
+ {
+ if (textBoxView.FindFirstParent() is { IsFocusable: true } tb)
+ {
+ var focusManager = VisualTree.GetFocusManagerForElement(tb);
+ focusManager?.UpdateFocus(new FocusMovement(tb, FocusNavigationDirection.None, FocusState.Pointer));
+ break;
+ }
+ }
else if (
- parent is FrameworkElement fe &&
+ candidate is FrameworkElement fe &&
(!fe.AllowFocusOnInteraction || !fe.IsTabStop))
{
// Stop propagating, this element does not want to receive focus.
break;
}
- else if (parent is Control control && control.IsFocusable)
+ else if (candidate is Control control && control.IsFocusable)
{
ProcessControlFocused(control);
break;
@@ -135,26 +167,6 @@ internal static void ReceiveFocusNative(int handle)
}
else if (focused != null)
{
- // Special handling for RootVisual - which is not focusable on managed side
- // but is focusable on native side. The purpose of this trick is to allow
- // us to recognize, that the page was focused by tabbing from the address bar
- // and focusing the first focusable element on the page instead.
- if (focused is RootVisual rootVisual)
- {
- var firstFocusable = FocusManager.FindFirstFocusableElement(rootVisual);
- if (firstFocusable is FrameworkElement frameworkElement)
- {
- if (_log.Value.IsEnabled(LogLevel.Debug))
- {
- _log.Value.LogDebug(
- $"Root visual focused - caused by browser keyboard navigation to the page, " +
- $"moving focus to actual first focusable element - {frameworkElement?.ToString() ?? "[null]"}.");
- }
- frameworkElement.Focus(FocusState.Keyboard);
- }
- return;
- }
-
ProcessElementFocused(focused);
}
else