Skip to content

Commit

Permalink
Merge pull request #14983 from ramezgerges/wasm_autosuggestbox_focus
Browse files Browse the repository at this point in the history
fix(textbox): fix textbox losing focus on WASM if alt-tabbed out then back in
  • Loading branch information
MartinZikmund authored Jan 17, 2024
2 parents 7e38d80 + 55940bd commit 9fa5c6c
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 24 deletions.
7 changes: 7 additions & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml\FocusManager\Focus_WASM_Tab.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml\FocusManager\Focus_FocusCycle.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -5980,6 +5984,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml\FocusManager\FocusManager_GetFocus_Automated.xaml.cs">
<DependentUpon>FocusManager_GetFocus_Automated.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml\FocusManager\Focus_WASM_Tab.xaml.cs">
<DependentUpon>Focus_WASM_Tab.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml\FocusManager\Focus_FocusCycle.xaml.cs">
<DependentUpon>Focus_FocusCycle.xaml</DependentUpon>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<UserControl x:Class="Uno.UI.Samples.Content.UITests.FocusTests.Focus_WASM_Tab"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="">
<StackPanel>
<TextBlock>
Focus the address bar then tab into the page. SplitView should be focused and not RootVisual.
</TextBlock>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="Currently focused control"
FontSize="22" />
<Border Background="LightGray"
Margin="0,8,0,0">
<TextBlock x:Name="TxtCurrentFocused"
Text="&lt;none&gt;"
FontSize="18"
FontWeight="Bold"
TextAlignment="Center"
Margin="0,6" />
</Border>
</StackPanel>
</StackPanel>
</UserControl>
Original file line number Diff line number Diff line change
@@ -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 ?? "<none>";
}
}
}
60 changes: 36 additions & 24 deletions src/Uno.UI/UI/Xaml/Input/FocusManager.wasm.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
{
Expand Down Expand Up @@ -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);
Expand All @@ -58,14 +81,23 @@ internal static void ProcessElementFocused(UIElement element)
_skipNativeFocus = false;
break;
}
else if (candidate is TextBoxView textBoxView)
{
if (textBoxView.FindFirstParent<TextBox>() 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;
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 9fa5c6c

Please sign in to comment.