From 328c52e2ad4e49f9fdd9006c88058a19857085e1 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 8 Jan 2024 13:05:47 +0200 Subject: [PATCH 1/2] fix(textbox): fix textbox losing focus on WASM if alt-tabbed out then back in --- src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs b/src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs index eb339b638a1b..7f14ebc9eba9 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; @@ -131,26 +163,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 From 55940bd217fc812631e401d82cc95e96ec034098 Mon Sep 17 00:00:00 2001 From: Ramez Ragaa Date: Mon, 15 Jan 2024 20:15:38 +0200 Subject: [PATCH 2/2] test: add a manual test to validate focus when tabbing from the address bar --- .../UITests.Shared/UITests.Shared.projitems | 7 ++++++ .../FocusManager/Focus_WASM_Tab.xaml | 24 +++++++++++++++++++ .../FocusManager/Focus_WASM_Tab.xaml.cs | 22 +++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml create mode 100644 src/SamplesApp/UITests.Shared/Windows_UI_Xaml/FocusManager/Focus_WASM_Tab.xaml.cs diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index 139239b1639f..cad056520883 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 @@ -5976,6 +5980,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 ?? ""; + } + } +}